From 9047ebbc229bf5b99d6c7522293b8cbd1100b747 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 12 Aug 2008 18:45:14 +0200 Subject: [PATCH 01/11] Split out merge_recursive() to merge-recursive.c Move most of the of code from builtin-merge-recursive.c to a new file merge-recursive.c and introduce merge_recursive_setup() in there so that builtin-merge-recursive and other builtins call it. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-merge-recursive.c | 1327 +----------------------------------- merge-recursive.c | 1331 +++++++++++++++++++++++++++++++++++++ merge-recursive.h | 6 +- 4 files changed, 1341 insertions(+), 1324 deletions(-) create mode 100644 merge-recursive.c diff --git a/Makefile b/Makefile index 20f028ffff..f697618384 100644 --- a/Makefile +++ b/Makefile @@ -440,6 +440,7 @@ LIB_OBJS += log-tree.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o +LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o LIB_OBJS += object.o LIB_OBJS += pack-check.o diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index dfb363ee2f..8bf2fa5df3 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -1,1307 +1,8 @@ -/* - * Recursive Merge algorithm stolen from git-merge-recursive.py by - * Fredrik Kuivinen. - * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 - */ #include "cache.h" -#include "cache-tree.h" #include "commit.h" -#include "blob.h" -#include "builtin.h" -#include "tree-walk.h" -#include "diff.h" -#include "diffcore.h" #include "tag.h" -#include "unpack-trees.h" -#include "string-list.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "interpolate.h" -#include "attr.h" #include "merge-recursive.h" -static int subtree_merge; - -static struct tree *shift_tree_object(struct tree *one, struct tree *two) -{ - unsigned char shifted[20]; - - /* - * NEEDSWORK: this limits the recursion depth to hardcoded - * value '2' to avoid excessive overhead. - */ - shift_tree(one->object.sha1, two->object.sha1, shifted, 2); - if (!hashcmp(two->object.sha1, shifted)) - return two; - return lookup_tree(shifted); -} - -/* - * A virtual commit has - * - (const char *)commit->util set to the name, and - * - *(int *)commit->object.sha1 set to the virtual id. - */ - -static struct commit *make_virtual_commit(struct tree *tree, const char *comment) -{ - struct commit *commit = xcalloc(1, sizeof(struct commit)); - static unsigned virtual_id = 1; - commit->tree = tree; - commit->util = (void*)comment; - *(int*)commit->object.sha1 = virtual_id++; - /* avoid warnings */ - commit->object.parsed = 1; - return commit; -} - -/* - * Since we use get_tree_entry(), which does not put the read object into - * the object pool, we cannot rely on a == b. - */ -static int sha_eq(const unsigned char *a, const unsigned char *b) -{ - if (!a && !b) - return 2; - return a && b && hashcmp(a, b) == 0; -} - -/* - * Since we want to write the index eventually, we cannot reuse the index - * for these (temporary) data. - */ -struct stage_data -{ - struct - { - unsigned mode; - unsigned char sha[20]; - } stages[4]; - unsigned processed:1; -}; - -static struct string_list current_file_set = {NULL, 0, 0, 1}; -static struct string_list current_directory_set = {NULL, 0, 0, 1}; - -static int call_depth = 0; -static int verbosity = 2; -static int diff_rename_limit = -1; -static int merge_rename_limit = -1; -static int buffer_output = 1; -static struct strbuf obuf = STRBUF_INIT; - -static int show(int v) -{ - return (!call_depth && verbosity >= v) || verbosity >= 5; -} - -static void flush_output(void) -{ - if (obuf.len) { - fputs(obuf.buf, stdout); - strbuf_reset(&obuf); - } -} - -static void output(int v, const char *fmt, ...) -{ - int len; - va_list ap; - - if (!show(v)) - return; - - strbuf_grow(&obuf, call_depth * 2 + 2); - memset(obuf.buf + obuf.len, ' ', call_depth * 2); - strbuf_setlen(&obuf, obuf.len + call_depth * 2); - - va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); - va_end(ap); - - if (len < 0) - len = 0; - if (len >= strbuf_avail(&obuf)) { - strbuf_grow(&obuf, len + 2); - va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&obuf)) { - die("this should not happen, your snprintf is broken"); - } - } - strbuf_setlen(&obuf, obuf.len + len); - strbuf_add(&obuf, "\n", 1); - if (!buffer_output) - flush_output(); -} - -static void output_commit_title(struct commit *commit) -{ - int i; - flush_output(); - for (i = call_depth; i--;) - fputs(" ", stdout); - if (commit->util) - printf("virtual %s\n", (char *)commit->util); - else { - printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); - if (parse_commit(commit) != 0) - printf("(bad commit)\n"); - else { - const char *s; - int len; - for (s = commit->buffer; *s; s++) - if (*s == '\n' && s[1] == '\n') { - s += 2; - break; - } - for (len = 0; s[len] && '\n' != s[len]; len++) - ; /* do nothing */ - printf("%.*s\n", len, s); - } - } -} - -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, - const char *path, int stage, int refresh, int options) -{ - struct cache_entry *ce; - ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); - if (!ce) - return error("addinfo_cache failed for path '%s'", path); - return add_cache_entry(ce, options); -} - -/* - * This is a global variable which is used in a number of places but - * only written to in the 'merge' function. - * - * index_only == 1 => Don't leave any non-stage 0 entries in the cache and - * don't update the working directory. - * 0 => Leave unmerged entries in the cache and update - * the working directory. - */ -static int index_only = 0; - -static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) -{ - parse_tree(tree); - init_tree_desc(desc, tree->buffer, tree->size); -} - -static int git_merge_trees(int index_only, - struct tree *common, - struct tree *head, - struct tree *merge) -{ - int rc; - struct tree_desc t[3]; - struct unpack_trees_options opts; - - memset(&opts, 0, sizeof(opts)); - if (index_only) - opts.index_only = 1; - else - opts.update = 1; - opts.merge = 1; - opts.head_idx = 2; - opts.fn = threeway_merge; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - init_tree_desc_from_tree(t+0, common); - init_tree_desc_from_tree(t+1, head); - init_tree_desc_from_tree(t+2, merge); - - rc = unpack_trees(3, t, &opts); - cache_tree_free(&active_cache_tree); - return rc; -} - -struct tree *write_tree_from_memory(void) -{ - struct tree *result = NULL; - - if (unmerged_cache()) { - int i; - output(0, "There are unmerged index entries:"); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) - output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); - } - return NULL; - } - - if (!active_cache_tree) - active_cache_tree = cache_tree(); - - if (!cache_tree_fully_valid(active_cache_tree) && - cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) - die("error building trees"); - - result = lookup_tree(active_cache_tree->sha1); - - return result; -} - -static int save_files_dirs(const unsigned char *sha1, - const char *base, int baselen, const char *path, - unsigned int mode, int stage, void *context) -{ - int len = strlen(path); - char *newpath = xmalloc(baselen + len + 1); - memcpy(newpath, base, baselen); - memcpy(newpath + baselen, path, len); - newpath[baselen + len] = '\0'; - - if (S_ISDIR(mode)) - string_list_insert(newpath, ¤t_directory_set); - else - string_list_insert(newpath, ¤t_file_set); - free(newpath); - - return READ_TREE_RECURSIVE; -} - -static int get_files_dirs(struct tree *tree) -{ - int n; - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL)) - return 0; - n = current_file_set.nr + current_directory_set.nr; - return n; -} - -/* - * Returns an index_entry instance which doesn't have to correspond to - * a real cache entry in Git's index. - */ -static struct stage_data *insert_stage_data(const char *path, - struct tree *o, struct tree *a, struct tree *b, - struct string_list *entries) -{ - struct string_list_item *item; - struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry(o->object.sha1, path, - e->stages[1].sha, &e->stages[1].mode); - get_tree_entry(a->object.sha1, path, - e->stages[2].sha, &e->stages[2].mode); - get_tree_entry(b->object.sha1, path, - e->stages[3].sha, &e->stages[3].mode); - item = string_list_insert(path, entries); - item->util = e; - return e; -} - -/* - * Create a dictionary mapping file names to stage_data objects. The - * dictionary contains one entry for every path with a non-zero stage entry. - */ -static struct string_list *get_unmerged(void) -{ - struct string_list *unmerged = xcalloc(1, sizeof(struct string_list)); - int i; - - unmerged->strdup_strings = 1; - - for (i = 0; i < active_nr; i++) { - struct string_list_item *item; - struct stage_data *e; - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - - item = string_list_lookup(ce->name, unmerged); - if (!item) { - item = string_list_insert(ce->name, unmerged); - item->util = xcalloc(1, sizeof(struct stage_data)); - } - e = item->util; - e->stages[ce_stage(ce)].mode = ce->ce_mode; - hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); - } - - return unmerged; -} - -struct rename -{ - struct diff_filepair *pair; - struct stage_data *src_entry; - struct stage_data *dst_entry; - unsigned processed:1; -}; - -/* - * Get information of all renames which occurred between 'o_tree' and - * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and - * 'b_tree') to be able to associate the correct cache entries with - * the rename information. 'tree' is always equal to either a_tree or b_tree. - */ -static struct string_list *get_renames(struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) -{ - int i; - struct string_list *renames; - struct diff_options opts; - - renames = xcalloc(1, sizeof(struct string_list)); - diff_setup(&opts); - DIFF_OPT_SET(&opts, RECURSIVE); - opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : - diff_rename_limit >= 0 ? diff_rename_limit : - 500; - opts.warn_on_too_large_rename = 1; - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - if (diff_setup_done(&opts) < 0) - die("diff setup failed"); - diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); - diffcore_std(&opts); - for (i = 0; i < diff_queued_diff.nr; ++i) { - struct string_list_item *item; - struct rename *re; - struct diff_filepair *pair = diff_queued_diff.queue[i]; - if (pair->status != 'R') { - diff_free_filepair(pair); - continue; - } - re = xmalloc(sizeof(*re)); - re->processed = 0; - re->pair = pair; - item = string_list_lookup(re->pair->one->path, entries); - if (!item) - re->src_entry = insert_stage_data(re->pair->one->path, - o_tree, a_tree, b_tree, entries); - else - re->src_entry = item->util; - - item = string_list_lookup(re->pair->two->path, entries); - if (!item) - re->dst_entry = insert_stage_data(re->pair->two->path, - o_tree, a_tree, b_tree, entries); - else - re->dst_entry = item->util; - item = string_list_insert(pair->one->path, renames); - item->util = re; - } - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_queued_diff.nr = 0; - diff_flush(&opts); - return renames; -} - -static int update_stages(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - int clear) -{ - int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; - if (clear) - if (remove_file_from_cache(path)) - return -1; - if (o) - if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) - return -1; - if (a) - if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) - return -1; - if (b) - if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) - return -1; - return 0; -} - -static int remove_path(const char *name) -{ - int ret; - char *slash, *dirs; - - ret = unlink(name); - if (ret) - return ret; - dirs = xstrdup(name); - while ((slash = strrchr(name, '/'))) { - *slash = '\0'; - if (rmdir(name) != 0) - break; - } - free(dirs); - return ret; -} - -static int remove_file(int clean, const char *path, int no_wd) -{ - int update_cache = index_only || clean; - int update_working_directory = !index_only && !no_wd; - - if (update_cache) { - if (remove_file_from_cache(path)) - return -1; - } - if (update_working_directory) { - unlink(path); - if (errno != ENOENT || errno != EISDIR) - return -1; - remove_path(path); - } - return 0; -} - -static char *unique_path(const char *path, const char *branch) -{ - char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); - int suffix = 0; - struct stat st; - char *p = newpath + strlen(path); - strcpy(newpath, path); - *(p++) = '~'; - strcpy(p, branch); - for (; *p; ++p) - if ('/' == *p) - *p = '_'; - while (string_list_has_string(¤t_file_set, newpath) || - string_list_has_string(¤t_directory_set, newpath) || - lstat(newpath, &st) == 0) - sprintf(p, "_%d", suffix++); - - string_list_insert(newpath, ¤t_file_set); - return newpath; -} - -static void flush_buffer(int fd, const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = write_in_full(fd, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("merge-recursive: %s", strerror(errno)); - } else if (!ret) { - die("merge-recursive: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int make_room_for_path(const char *path) -{ - int status; - const char *msg = "failed to create path '%s'%s"; - - status = safe_create_leading_directories_const(path); - if (status) { - if (status == -3) { - /* something else exists */ - error(msg, path, ": perhaps a D/F conflict?"); - return -1; - } - die(msg, path, ""); - } - - /* Successful unlink is good.. */ - if (!unlink(path)) - return 0; - /* .. and so is no existing file */ - if (errno == ENOENT) - return 0; - /* .. but not some other error (who really cares what?) */ - return error(msg, path, ": perhaps a D/F conflict?"); -} - -static void update_file_flags(const unsigned char *sha, - unsigned mode, - const char *path, - int update_cache, - int update_wd) -{ - if (index_only) - update_wd = 0; - - if (update_wd) { - enum object_type type; - void *buf; - unsigned long size; - - if (S_ISGITLINK(mode)) - die("cannot read object %s '%s': It is a submodule!", - sha1_to_hex(sha), path); - - buf = read_sha1_file(sha, &type, &size); - if (!buf) - die("cannot read object %s '%s'", sha1_to_hex(sha), path); - if (type != OBJ_BLOB) - die("blob expected for %s '%s'", sha1_to_hex(sha), path); - if (S_ISREG(mode)) { - struct strbuf strbuf; - strbuf_init(&strbuf, 0); - if (convert_to_working_tree(path, buf, size, &strbuf)) { - free(buf); - size = strbuf.len; - buf = strbuf_detach(&strbuf, NULL); - } - } - - if (make_room_for_path(path) < 0) { - update_wd = 0; - free(buf); - goto update_index; - } - if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) { - int fd; - if (mode & 0100) - mode = 0777; - else - mode = 0666; - fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); - if (fd < 0) - die("failed to open %s: %s", path, strerror(errno)); - flush_buffer(fd, buf, size); - close(fd); - } else if (S_ISLNK(mode)) { - char *lnk = xmemdupz(buf, size); - safe_create_leading_directories_const(path); - unlink(path); - symlink(lnk, path); - free(lnk); - } else - die("do not know what to do with %06o %s '%s'", - mode, sha1_to_hex(sha), path); - free(buf); - } - update_index: - if (update_cache) - add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); -} - -static void update_file(int clean, - const unsigned char *sha, - unsigned mode, - const char *path) -{ - update_file_flags(sha, mode, path, index_only || clean, !index_only); -} - -/* Low level file merging, update and removal */ - -struct merge_file_info -{ - unsigned char sha[20]; - unsigned mode; - unsigned clean:1, - merge:1; -}; - -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - -static int merge_3way(mmbuffer_t *result_buf, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b, - const char *branch1, - const char *branch2) -{ - mmfile_t orig, src1, src2; - char *name1, *name2; - int merge_status; - - name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); - name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); - - fill_mm(o->sha1, &orig); - fill_mm(a->sha1, &src1); - fill_mm(b->sha1, &src2); - - merge_status = ll_merge(result_buf, a->path, &orig, - &src1, name1, &src2, name2, - index_only); - - free(name1); - free(name2); - free(orig.ptr); - free(src1.ptr); - free(src2.ptr); - return merge_status; -} - -static struct merge_file_info merge_file(struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - const char *branch1, const char *branch2) -{ - struct merge_file_info result; - result.merge = 0; - result.clean = 1; - - if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { - result.clean = 0; - if (S_ISREG(a->mode)) { - result.mode = a->mode; - hashcpy(result.sha, a->sha1); - } else { - result.mode = b->mode; - hashcpy(result.sha, b->sha1); - } - } else { - if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) - result.merge = 1; - - /* - * Merge modes - */ - if (a->mode == b->mode || a->mode == o->mode) - result.mode = b->mode; - else { - result.mode = a->mode; - if (b->mode != o->mode) { - result.clean = 0; - result.merge = 1; - } - } - - if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) - hashcpy(result.sha, b->sha1); - else if (sha_eq(b->sha1, o->sha1)) - hashcpy(result.sha, a->sha1); - else if (S_ISREG(a->mode)) { - mmbuffer_t result_buf; - int merge_status; - - merge_status = merge_3way(&result_buf, o, a, b, - branch1, branch2); - - if ((merge_status < 0) || !result_buf.ptr) - die("Failed to execute internal merge"); - - if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, result.sha)) - die("Unable to add %s to database", - a->path); - - free(result_buf.ptr); - result.clean = (merge_status == 0); - } else if (S_ISGITLINK(a->mode)) { - result.clean = 0; - hashcpy(result.sha, a->sha1); - } else if (S_ISLNK(a->mode)) { - hashcpy(result.sha, a->sha1); - - if (!sha_eq(a->sha1, b->sha1)) - result.clean = 0; - } else { - die("unsupported object type in the tree"); - } - } - - return result; -} - -static void conflict_rename_rename(struct rename *ren1, - const char *branch1, - struct rename *ren2, - const char *branch2) -{ - char *del[2]; - int delp = 0; - const char *ren1_dst = ren1->pair->two->path; - const char *ren2_dst = ren2->pair->two->path; - const char *dst_name1 = ren1_dst; - const char *dst_name2 = ren2_dst; - if (string_list_has_string(¤t_directory_set, ren1_dst)) { - dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s adding as %s instead", - ren1_dst, branch2, dst_name1); - remove_file(0, ren1_dst, 0); - } - if (string_list_has_string(¤t_directory_set, ren2_dst)) { - dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s adding as %s instead", - ren2_dst, branch1, dst_name2); - remove_file(0, ren2_dst, 0); - } - if (index_only) { - remove_file_from_cache(dst_name1); - remove_file_from_cache(dst_name2); - /* - * Uncomment to leave the conflicting names in the resulting tree - * - * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); - * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); - */ - } else { - update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); - update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); - } - while (delp--) - free(del[delp]); -} - -static void conflict_rename_dir(struct rename *ren1, - const char *branch1) -{ - char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); - free(new_path); -} - -static void conflict_rename_rename_2(struct rename *ren1, - const char *branch1, - struct rename *ren2, - const char *branch2) -{ - char *new_path1 = unique_path(ren1->pair->two->path, branch1); - char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renaming %s to %s and %s to %s instead", - ren1->pair->one->path, new_path1, - ren2->pair->one->path, new_path2); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); - update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); - free(new_path2); - free(new_path1); -} - -static int process_renames(struct string_list *a_renames, - struct string_list *b_renames, - const char *a_branch, - const char *b_branch) -{ - int clean_merge = 1, i, j; - struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; - const struct rename *sre; - - for (i = 0; i < a_renames->nr; i++) { - sre = a_renames->items[i].util; - string_list_insert(sre->pair->two->path, &a_by_dst)->util - = sre->dst_entry; - } - for (i = 0; i < b_renames->nr; i++) { - sre = b_renames->items[i].util; - string_list_insert(sre->pair->two->path, &b_by_dst)->util - = sre->dst_entry; - } - - for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { - int compare; - char *src; - struct string_list *renames1, *renames2, *renames2Dst; - struct rename *ren1 = NULL, *ren2 = NULL; - const char *branch1, *branch2; - const char *ren1_src, *ren1_dst; - - if (i >= a_renames->nr) { - compare = 1; - ren2 = b_renames->items[j++].util; - } else if (j >= b_renames->nr) { - compare = -1; - ren1 = a_renames->items[i++].util; - } else { - compare = strcmp(a_renames->items[i].string, - b_renames->items[j].string); - if (compare <= 0) - ren1 = a_renames->items[i++].util; - if (compare >= 0) - ren2 = b_renames->items[j++].util; - } - - /* TODO: refactor, so that 1/2 are not needed */ - if (ren1) { - renames1 = a_renames; - renames2 = b_renames; - renames2Dst = &b_by_dst; - branch1 = a_branch; - branch2 = b_branch; - } else { - struct rename *tmp; - renames1 = b_renames; - renames2 = a_renames; - renames2Dst = &a_by_dst; - branch1 = b_branch; - branch2 = a_branch; - tmp = ren2; - ren2 = ren1; - ren1 = tmp; - } - src = ren1->pair->one->path; - - ren1->dst_entry->processed = 1; - ren1->src_entry->processed = 1; - - if (ren1->processed) - continue; - ren1->processed = 1; - - ren1_src = ren1->pair->one->path; - ren1_dst = ren1->pair->two->path; - - if (ren2) { - const char *ren2_src = ren2->pair->one->path; - const char *ren2_dst = ren2->pair->two->path; - /* Renamed in 1 and renamed in 2 */ - if (strcmp(ren1_src, ren2_src) != 0) - die("ren1.src != ren2.src"); - ren2->dst_entry->processed = 1; - ren2->processed = 1; - if (strcmp(ren1_dst, ren2_dst) != 0) { - clean_merge = 0; - output(1, "CONFLICT (rename/rename): " - "Rename \"%s\"->\"%s\" in branch \"%s\" " - "rename \"%s\"->\"%s\" in \"%s\"%s", - src, ren1_dst, branch1, - src, ren2_dst, branch2, - index_only ? " (left unresolved)": ""); - if (index_only) { - remove_file_from_cache(src); - update_file(0, ren1->pair->one->sha1, - ren1->pair->one->mode, src); - } - conflict_rename_rename(ren1, branch1, ren2, branch2); - } else { - struct merge_file_info mfi; - remove_file(1, ren1_src, 1); - mfi = merge_file(ren1->pair->one, - ren1->pair->two, - ren2->pair->two, - branch1, - branch2); - if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); - - if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); - - if (!mfi.clean) { - output(1, "CONFLICT (content): merge conflict in %s", - ren1_dst); - clean_merge = 0; - - if (!index_only) - update_stages(ren1_dst, - ren1->pair->one, - ren1->pair->two, - ren2->pair->two, - 1 /* clear */); - } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); - } - } else { - /* Renamed in 1, maybe changed in 2 */ - struct string_list_item *item; - /* we only use sha1 and mode of these */ - struct diff_filespec src_other, dst_other; - int try_merge, stage = a_renames == renames1 ? 3: 2; - - remove_file(1, ren1_src, index_only || stage == 3); - - hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); - src_other.mode = ren1->src_entry->stages[stage].mode; - hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); - dst_other.mode = ren1->dst_entry->stages[stage].mode; - - try_merge = 0; - - if (string_list_has_string(¤t_directory_set, ren1_dst)) { - clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " - " directory %s added in %s", - ren1_src, ren1_dst, branch1, - ren1_dst, branch2); - conflict_rename_dir(ren1, branch1); - } else if (sha_eq(src_other.sha1, null_sha1)) { - clean_merge = 0; - output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " - "and deleted in %s", - ren1_src, ren1_dst, branch1, - branch2); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); - } else if (!sha_eq(dst_other.sha1, null_sha1)) { - const char *new_path; - clean_merge = 0; - try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " - "%s added in %s", - ren1_src, ren1_dst, branch1, - ren1_dst, branch2); - new_path = unique_path(ren1_dst, branch2); - output(1, "Adding as %s instead", new_path); - update_file(0, dst_other.sha1, dst_other.mode, new_path); - } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { - ren2 = item->util; - clean_merge = 0; - ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " - "Rename %s->%s in %s", - ren1_src, ren1_dst, branch1, - ren2->pair->one->path, ren2->pair->two->path, branch2); - conflict_rename_rename_2(ren1, branch1, ren2, branch2); - } else - try_merge = 1; - - if (try_merge) { - struct diff_filespec *o, *a, *b; - struct merge_file_info mfi; - src_other.path = (char *)ren1_src; - - o = ren1->pair->one; - if (a_renames == renames1) { - a = ren1->pair->two; - b = &src_other; - } else { - b = ren1->pair->two; - a = &src_other; - } - mfi = merge_file(o, a, b, - a_branch, b_branch); - - if (mfi.clean && - sha_eq(mfi.sha, ren1->pair->two->sha1) && - mfi.mode == ren1->pair->two->mode) - /* - * This messaged is part of - * t6022 test. If you change - * it update the test too. - */ - output(3, "Skipped %s (merged same as existing)", ren1_dst); - else { - if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); - if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); - if (!mfi.clean) { - output(1, "CONFLICT (rename/modify): Merge conflict in %s", - ren1_dst); - clean_merge = 0; - - if (!index_only) - update_stages(ren1_dst, - o, a, b, 1); - } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); - } - } - } - } - string_list_clear(&a_by_dst, 0); - string_list_clear(&b_by_dst, 0); - - return clean_merge; -} - -static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) -{ - return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; -} - -/* Per entry merge function */ -static int process_entry(const char *path, struct stage_data *entry, - const char *branch1, - const char *branch2) -{ - /* - printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); - print_index_entry("\tpath: ", entry); - */ - int clean_merge = 1; - unsigned o_mode = entry->stages[1].mode; - unsigned a_mode = entry->stages[2].mode; - unsigned b_mode = entry->stages[3].mode; - unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); - unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); - unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); - - if (o_sha && (!a_sha || !b_sha)) { - /* Case A: Deleted in one */ - if ((!a_sha && !b_sha) || - (sha_eq(a_sha, o_sha) && !b_sha) || - (!a_sha && sha_eq(b_sha, o_sha))) { - /* Deleted in both or deleted in one and - * unchanged in the other */ - if (a_sha) - output(2, "Removing %s", path); - /* do not touch working file if it did not exist */ - remove_file(1, path, !a_sha); - } else { - /* Deleted in one and changed in the other */ - clean_merge = 0; - if (!a_sha) { - output(1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree.", - path, branch1, - branch2, branch2, path); - update_file(0, b_sha, b_mode, path); - } else { - output(1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree.", - path, branch2, - branch1, branch1, path); - update_file(0, a_sha, a_mode, path); - } - } - - } else if ((!o_sha && a_sha && !b_sha) || - (!o_sha && !a_sha && b_sha)) { - /* Case B: Added in one. */ - const char *add_branch; - const char *other_branch; - unsigned mode; - const unsigned char *sha; - const char *conf; - - if (a_sha) { - add_branch = branch1; - other_branch = branch2; - mode = a_mode; - sha = a_sha; - conf = "file/directory"; - } else { - add_branch = branch2; - other_branch = branch1; - mode = b_mode; - sha = b_sha; - conf = "directory/file"; - } - if (string_list_has_string(¤t_directory_set, path)) { - const char *new_path = unique_path(path, add_branch); - clean_merge = 0; - output(1, "CONFLICT (%s): There is a directory with name %s in %s. " - "Adding %s as %s", - conf, path, other_branch, path, new_path); - remove_file(0, path, 0); - update_file(0, sha, mode, new_path); - } else { - output(2, "Adding %s", path); - update_file(1, sha, mode, path); - } - } else if (a_sha && b_sha) { - /* Case C: Added in both (check for same permissions) and */ - /* case D: Modified in both, but differently. */ - const char *reason = "content"; - struct merge_file_info mfi; - struct diff_filespec o, a, b; - - if (!o_sha) { - reason = "add/add"; - o_sha = (unsigned char *)null_sha1; - } - output(2, "Auto-merging %s", path); - o.path = a.path = b.path = (char *)path; - hashcpy(o.sha1, o_sha); - o.mode = o_mode; - hashcpy(a.sha1, a_sha); - a.mode = a_mode; - hashcpy(b.sha1, b_sha); - b.mode = b_mode; - - mfi = merge_file(&o, &a, &b, - branch1, branch2); - - clean_merge = mfi.clean; - if (mfi.clean) - update_file(1, mfi.sha, mfi.mode, path); - else if (S_ISGITLINK(mfi.mode)) - output(1, "CONFLICT (submodule): Merge conflict in %s " - "- needs %s", path, sha1_to_hex(b.sha1)); - else { - output(1, "CONFLICT (%s): Merge conflict in %s", - reason, path); - - if (index_only) - update_file(0, mfi.sha, mfi.mode, path); - else - update_file_flags(mfi.sha, mfi.mode, path, - 0 /* update_cache */, 1 /* update_working_directory */); - } - } else if (!o_sha && !a_sha && !b_sha) { - /* - * this entry was deleted altogether. a_mode == 0 means - * we had that path and want to actively remove it. - */ - remove_file(1, path, !a_mode); - } else - die("Fatal merge failure, shouldn't happen."); - - return clean_merge; -} - -int merge_trees(struct tree *head, - struct tree *merge, - struct tree *common, - const char *branch1, - const char *branch2, - struct tree **result) -{ - int code, clean; - - if (subtree_merge) { - merge = shift_tree_object(head, merge); - common = shift_tree_object(head, common); - } - - if (sha_eq(common->object.sha1, merge->object.sha1)) { - output(0, "Already uptodate!"); - *result = head; - return 1; - } - - code = git_merge_trees(index_only, common, head, merge); - - if (code != 0) - die("merging of trees %s and %s failed", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); - - if (unmerged_cache()) { - struct string_list *entries, *re_head, *re_merge; - int i; - string_list_clear(¤t_file_set, 1); - string_list_clear(¤t_directory_set, 1); - get_files_dirs(head); - get_files_dirs(merge); - - entries = get_unmerged(); - re_head = get_renames(head, common, head, merge, entries); - re_merge = get_renames(merge, common, head, merge, entries); - clean = process_renames(re_head, re_merge, - branch1, branch2); - for (i = 0; i < entries->nr; i++) { - const char *path = entries->items[i].string; - struct stage_data *e = entries->items[i].util; - if (!e->processed - && !process_entry(path, e, branch1, branch2)) - clean = 0; - } - - string_list_clear(re_merge, 0); - string_list_clear(re_head, 0); - string_list_clear(entries, 1); - - } - else - clean = 1; - - if (index_only) - *result = write_tree_from_memory(); - - return clean; -} - -static struct commit_list *reverse_commit_list(struct commit_list *list) -{ - struct commit_list *next = NULL, *current, *backup; - for (current = list; current; current = backup) { - backup = current->next; - current->next = next; - next = current; - } - return next; -} - -/* - * Merge the commits h1 and h2, return the resulting virtual - * commit object and a flag indicating the cleanness of the merge. - */ -int merge_recursive(struct commit *h1, - struct commit *h2, - const char *branch1, - const char *branch2, - struct commit_list *ca, - struct commit **result) -{ - struct commit_list *iter; - struct commit *merged_common_ancestors; - struct tree *mrtree = mrtree; - int clean; - - if (show(4)) { - output(4, "Merging:"); - output_commit_title(h1); - output_commit_title(h2); - } - - if (!ca) { - ca = get_merge_bases(h1, h2, 1); - ca = reverse_commit_list(ca); - } - - if (show(5)) { - output(5, "found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); - } - - merged_common_ancestors = pop_commit(&ca); - if (merged_common_ancestors == NULL) { - /* if there is no common ancestor, make an empty tree */ - struct tree *tree = xcalloc(1, sizeof(struct tree)); - - tree->object.parsed = 1; - tree->object.type = OBJ_TREE; - pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); - merged_common_ancestors = make_virtual_commit(tree, "ancestor"); - } - - for (iter = ca; iter; iter = iter->next) { - call_depth++; - /* - * When the merge fails, the result contains files - * with conflict markers. The cleanness flag is - * ignored, it was never actually used, as result of - * merge_trees has always overwritten it: the committed - * "conflicts" were already resolved. - */ - discard_cache(); - merge_recursive(merged_common_ancestors, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - NULL, - &merged_common_ancestors); - call_depth--; - - if (!merged_common_ancestors) - die("merge returned no commit"); - } - - discard_cache(); - if (!call_depth) { - read_cache(); - index_only = 0; - } else - index_only = 1; - - clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, - branch1, branch2, &mrtree); - - if (index_only) { - *result = make_virtual_commit(mrtree, "merged tree"); - commit_list_insert(h1, &(*result)->parents); - commit_list_insert(h2, &(*result)->parents->next); - } - flush_output(); - return clean; -} - static const char *better_branch_name(const char *branch) { static char githead_env[8 + 40 + 1]; @@ -1334,23 +35,6 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } -static int merge_config(const char *var, const char *value, void *cb) -{ - if (!strcasecmp(var, "merge.verbosity")) { - verbosity = git_config_int(var, value); - return 0; - } - if (!strcasecmp(var, "diff.renamelimit")) { - diff_rename_limit = git_config_int(var, value); - return 0; - } - if (!strcasecmp(var, "merge.renamelimit")) { - merge_rename_limit = git_config_int(var, value); - return 0; - } - return git_default_config(var, value, cb); -} - int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { static const char *bases[20]; @@ -1361,6 +45,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) struct commit_list *ca = NULL; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; + int subtree_merge = 0; if (argv[0]) { int namelen = strlen(argv[0]); @@ -1369,10 +54,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) subtree_merge = 1; } - git_config(merge_config, NULL); - if (getenv("GIT_MERGE_VERBOSITY")) - verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); - + git_config(merge_recursive_config, NULL); + merge_recursive_setup(subtree_merge); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); @@ -1384,8 +67,6 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); - if (verbosity >= 5) - buffer_output = 0; branch1 = argv[++i]; branch2 = argv[++i]; @@ -1396,7 +77,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - if (show(3)) + if (merge_recursive_verbosity >= 3) printf("Merging %s with %s\n", branch1, branch2); index_fd = hold_locked_index(lock, 1); diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 0000000000..a0cb3f47de --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1331 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "builtin.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "tag.h" +#include "unpack-trees.h" +#include "string-list.h" +#include "xdiff-interface.h" +#include "ll-merge.h" +#include "interpolate.h" +#include "attr.h" +#include "merge-recursive.h" + +static int subtree_merge; + +static struct tree *shift_tree_object(struct tree *one, struct tree *two) +{ + unsigned char shifted[20]; + + /* + * NEEDSWORK: this limits the recursion depth to hardcoded + * value '2' to avoid excessive overhead. + */ + shift_tree(one->object.sha1, two->object.sha1, shifted, 2); + if (!hashcmp(two->object.sha1, shifted)) + return two; + return lookup_tree(shifted); +} + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ + +struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; + return commit; +} + +/* + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if (!a && !b) + return 2; + return a && b && hashcmp(a, b) == 0; +} + +/* + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct string_list current_file_set = {NULL, 0, 0, 1}; +static struct string_list current_directory_set = {NULL, 0, 0, 1}; + +static int call_depth = 0; +int merge_recursive_verbosity = 2; +static int diff_rename_limit = -1; +static int merge_rename_limit = -1; +static int buffer_output = 1; +static struct strbuf obuf = STRBUF_INIT; + +static int show(int v) +{ + return (!call_depth && merge_recursive_verbosity >= v) || + merge_recursive_verbosity >= 5; +} + +static void flush_output(void) +{ + if (obuf.len) { + fputs(obuf.buf, stdout); + strbuf_reset(&obuf); + } +} + +static void output(int v, const char *fmt, ...) +{ + int len; + va_list ap; + + if (!show(v)) + return; + + strbuf_grow(&obuf, call_depth * 2 + 2); + memset(obuf.buf + obuf.len, ' ', call_depth * 2); + strbuf_setlen(&obuf, obuf.len + call_depth * 2); + + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + + if (len < 0) + len = 0; + if (len >= strbuf_avail(&obuf)) { + strbuf_grow(&obuf, len + 2); + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&obuf)) { + die("this should not happen, your snprintf is broken"); + } + } + strbuf_setlen(&obuf, obuf.len + len); + strbuf_add(&obuf, "\n", 1); + if (!buffer_output) + flush_output(); +} + +static void output_commit_title(struct commit *commit) +{ + int i; + flush_output(); + for (i = call_depth; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("addinfo_cache failed for path '%s'", path); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) +{ + parse_tree(tree); + init_tree_desc(desc, tree->buffer, tree->size); +} + +static int git_merge_trees(int index_only, + struct tree *common, + struct tree *head, + struct tree *merge) +{ + int rc; + struct tree_desc t[3]; + struct unpack_trees_options opts; + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + opts.src_index = &the_index; + opts.dst_index = &the_index; + + init_tree_desc_from_tree(t+0, common); + init_tree_desc_from_tree(t+1, head); + init_tree_desc_from_tree(t+2, merge); + + rc = unpack_trees(3, t, &opts); + cache_tree_free(&active_cache_tree); + return rc; +} + +struct tree *write_tree_from_memory(void) +{ + struct tree *result = NULL; + + if (unmerged_cache()) { + int i; + output(0, "There are unmerged index entries:"); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); + } + return NULL; + } + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + return result; +} + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage, void *context) +{ + int len = strlen(path); + char *newpath = xmalloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + string_list_insert(newpath, ¤t_directory_set); + else + string_list_insert(newpath, ¤t_file_set); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree) +{ + int n; + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL)) + return 0; + n = current_file_set.nr + current_directory_set.nr; + return n; +} + +/* + * Returns an index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct string_list *entries) +{ + struct string_list_item *item; + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + item = string_list_insert(path, entries); + item->util = e; + return e; +} + +/* + * Create a dictionary mapping file names to stage_data objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct string_list *get_unmerged(void) +{ + struct string_list *unmerged = xcalloc(1, sizeof(struct string_list)); + int i; + + unmerged->strdup_strings = 1; + + for (i = 0; i < active_nr; i++) { + struct string_list_item *item; + struct stage_data *e; + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + item = string_list_lookup(ce->name, unmerged); + if (!item) { + item = string_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; + e->stages[ce_stage(ce)].mode = ce->ce_mode; + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + } + + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +/* + * Get information of all renames which occurred between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. + */ +static struct string_list *get_renames(struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries) +{ + int i; + struct string_list *renames; + struct diff_options opts; + + renames = xcalloc(1, sizeof(struct string_list)); + diff_setup(&opts); + DIFF_OPT_SET(&opts, RECURSIVE); + opts.detect_rename = DIFF_DETECT_RENAME; + opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : + diff_rename_limit >= 0 ? diff_rename_limit : + 500; + opts.warn_on_too_large_rename = 1; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct string_list_item *item; + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + item = string_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = string_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = string_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + return renames; +} + +static int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + int clear) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if (clear) + if (remove_file_from_cache(path)) + return -1; + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + return -1; + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + return -1; + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + return -1; + return 0; +} + +static int remove_path(const char *name) +{ + int ret; + char *slash, *dirs; + + ret = unlink(name); + if (ret) + return ret; + dirs = xstrdup(name); + while ((slash = strrchr(name, '/'))) { + *slash = '\0'; + if (rmdir(name) != 0) + break; + } + free(dirs); + return ret; +} + +static int remove_file(int clean, const char *path, int no_wd) +{ + int update_cache = index_only || clean; + int update_working_directory = !index_only && !no_wd; + + if (update_cache) { + if (remove_file_from_cache(path)) + return -1; + } + if (update_working_directory) { + unlink(path); + if (errno != ENOENT || errno != EISDIR) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(path); + strcpy(newpath, path); + *(p++) = '~'; + strcpy(p, branch); + for (; *p; ++p) + if ('/' == *p) + *p = '_'; + while (string_list_has_string(¤t_file_set, newpath) || + string_list_has_string(¤t_directory_set, newpath) || + lstat(newpath, &st) == 0) + sprintf(p, "_%d", suffix++); + + string_list_insert(newpath, ¤t_file_set); + return newpath; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = write_in_full(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +static int make_room_for_path(const char *path) +{ + int status; + const char *msg = "failed to create path '%s'%s"; + + status = safe_create_leading_directories_const(path); + if (status) { + if (status == -3) { + /* something else exists */ + error(msg, path, ": perhaps a D/F conflict?"); + return -1; + } + die(msg, path, ""); + } + + /* Successful unlink is good.. */ + if (!unlink(path)) + return 0; + /* .. and so is no existing file */ + if (errno == ENOENT) + return 0; + /* .. but not some other error (who really cares what?) */ + return error(msg, path, ": perhaps a D/F conflict?"); +} + +static void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) +{ + if (index_only) + update_wd = 0; + + if (update_wd) { + enum object_type type; + void *buf; + unsigned long size; + + if (S_ISGITLINK(mode)) + die("cannot read object %s '%s': It is a submodule!", + sha1_to_hex(sha), path); + + buf = read_sha1_file(sha, &type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if (type != OBJ_BLOB) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + if (S_ISREG(mode)) { + struct strbuf strbuf; + strbuf_init(&strbuf, 0); + if (convert_to_working_tree(path, buf, size, &strbuf)) { + free(buf); + size = strbuf.len; + buf = strbuf_detach(&strbuf, NULL); + } + } + + if (make_room_for_path(path) < 0) { + update_wd = 0; + free(buf); + goto update_index; + } + if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) { + int fd; + if (mode & 0100) + mode = 0777; + else + mode = 0666; + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if (S_ISLNK(mode)) { + char *lnk = xmemdupz(buf, size); + safe_create_leading_directories_const(path); + unlink(path); + symlink(lnk, path); + free(lnk); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + free(buf); + } + update_index: + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); +} + +static void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } + + mm->ptr = read_sha1_file(sha1, &type, &size); + if (!mm->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; +} + +static int merge_3way(mmbuffer_t *result_buf, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) +{ + mmfile_t orig, src1, src2; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + merge_status = ll_merge(result_buf, a->path, &orig, + &src1, name1, &src2, name2, + index_only); + + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + return merge_status; +} + +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1, const char *branch2) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { + result.clean = 0; + if (S_ISREG(a->mode)) { + result.mode = a->mode; + hashcpy(result.sha, a->sha1); + } else { + result.mode = b->mode; + hashcpy(result.sha, b->sha1); + } + } else { + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + result.merge = 1; + + /* + * Merge modes + */ + if (a->mode == b->mode || a->mode == o->mode) + result.mode = b->mode; + else { + result.mode = a->mode; + if (b->mode != o->mode) { + result.clean = 0; + result.merge = 1; + } + } + + if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) + hashcpy(result.sha, b->sha1); + else if (sha_eq(b->sha1, o->sha1)) + hashcpy(result.sha, a->sha1); + else if (S_ISREG(a->mode)) { + mmbuffer_t result_buf; + int merge_status; + + merge_status = merge_3way(&result_buf, o, a, b, + branch1, branch2); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); + } else if (S_ISGITLINK(a->mode)) { + result.clean = 0; + hashcpy(result.sha, a->sha1); + } else if (S_ISLNK(a->mode)) { + hashcpy(result.sha, a->sha1); + + if (!sha_eq(a->sha1, b->sha1)) + result.clean = 0; + } else { + die("unsupported object type in the tree"); + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (string_list_has_string(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + output(1, "%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dst_name1); + remove_file(0, ren1_dst, 0); + } + if (string_list_has_string(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + output(1, "%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dst_name2); + remove_file(0, ren2_dst, 0); + } + if (index_only) { + remove_file_from_cache(dst_name1); + remove_file_from_cache(dst_name2); + /* + * Uncomment to leave the conflicting names in the resulting tree + * + * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); + * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); + */ + } else { + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); + } + while (delp--) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *new_path = unique_path(ren1->pair->two->path, branch1); + output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); + remove_file(0, ren1->pair->two->path, 0); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); + output(1, "Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); + remove_file(0, ren1->pair->two->path, 0); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); +} + +static int process_renames(struct string_list *a_renames, + struct string_list *b_renames, + const char *a_branch, + const char *b_branch) +{ + int clean_merge = 1, i, j; + struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + string_list_insert(sre->pair->two->path, &a_by_dst)->util + = sre->dst_entry; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + string_list_insert(sre->pair->two->path, &b_by_dst)->util + = sre->dst_entry; + } + + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { + int compare; + char *src; + struct string_list *renames1, *renames2, *renames2Dst; + struct rename *ren1 = NULL, *ren2 = NULL; + const char *branch1, *branch2; + const char *ren1_src, *ren1_dst; + + if (i >= a_renames->nr) { + compare = 1; + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { + compare = -1; + ren1 = a_renames->items[i++].util; + } else { + compare = strcmp(a_renames->items[i].string, + b_renames->items[j].string); + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; + } + + /* TODO: refactor, so that 1/2 are not needed */ + if (ren1) { + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; + } else { + struct rename *tmp; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; + tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + src = ren1->pair->one->path; + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if (ren1->processed) + continue; + ren1->processed = 1; + + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; + + if (ren2) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + clean_merge = 0; + output(1, "CONFLICT (rename/rename): " + "Rename \"%s\"->\"%s\" in branch \"%s\" " + "rename \"%s\"->\"%s\" in \"%s\"%s", + src, ren1_dst, branch1, + src, ren2_dst, branch2, + index_only ? " (left unresolved)": ""); + if (index_only) { + remove_file_from_cache(src); + update_file(0, ren1->pair->one->sha1, + ren1->pair->one->mode, src); + } + conflict_rename_rename(ren1, branch1, ren2, branch2); + } else { + struct merge_file_info mfi; + remove_file(1, ren1_src, 1); + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + branch1, + branch2); + if (mfi.merge || !mfi.clean) + output(1, "Renaming %s->%s", src, ren1_dst); + + if (mfi.merge) + output(2, "Auto-merging %s", ren1_dst); + + if (!mfi.clean) { + output(1, "CONFLICT (content): merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + struct string_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int try_merge, stage = a_renames == renames1 ? 3: 2; + + remove_file(1, ren1_src, index_only || stage == 3); + + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); + src_other.mode = ren1->src_entry->stages[stage].mode; + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); + dst_other.mode = ren1->dst_entry->stages[stage].mode; + + try_merge = 0; + + if (string_list_has_string(¤t_directory_set, ren1_dst)) { + clean_merge = 0; + output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); + } else if (sha_eq(src_other.sha1, null_sha1)) { + clean_merge = 0; + output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branch1, + branch2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *new_path; + clean_merge = 0; + try_merge = 1; + output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output(1, "Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); + } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; + clean_merge = 0; + ren2->processed = 1; + output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); + } else + try_merge = 1; + + if (try_merge) { + struct diff_filespec *o, *a, *b; + struct merge_file_info mfi; + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (a_renames == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + a_branch, b_branch); + + if (mfi.clean && + sha_eq(mfi.sha, ren1->pair->two->sha1) && + mfi.mode == ren1->pair->two->mode) + /* + * This messaged is part of + * t6022 test. If you change + * it update the test too. + */ + output(3, "Skipped %s (merged same as existing)", ren1_dst); + else { + if (mfi.merge || !mfi.clean) + output(1, "Renaming %s => %s", ren1_src, ren1_dst); + if (mfi.merge) + output(2, "Auto-merging %s", ren1_dst); + if (!mfi.clean) { + output(1, "CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + o, a, b, 1); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + } + string_list_clear(&a_by_dst, 0); + string_list_clear(&b_by_dst, 0); + + return clean_merge; +} + +static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) +{ + return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1, + const char *branch2) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int clean_merge = 1; + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); + unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); + unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); + + if (o_sha && (!a_sha || !b_sha)) { + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output(2, "Removing %s", path); + /* do not touch working file if it did not exist */ + remove_file(1, path, !a_sha); + } else { + /* Deleted in one and changed in the other */ + clean_merge = 0; + if (!a_sha) { + output(1, "CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1, + branch2, branch2, path); + update_file(0, b_sha, b_mode, path); + } else { + output(1, "CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2, + branch1, branch1, path); + update_file(0, a_sha, a_mode, path); + } + } + + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if (a_sha) { + add_branch = branch1; + other_branch = branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = branch2; + other_branch = branch1; + mode = b_mode; + sha = b_sha; + conf = "directory/file"; + } + if (string_list_has_string(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; + output(1, "CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, other_branch, path, new_path); + remove_file(0, path, 0); + update_file(0, sha, mode, new_path); + } else { + output(2, "Adding %s", path); + update_file(1, sha, mode, path); + } + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ + /* case D: Modified in both, but differently. */ + const char *reason = "content"; + struct merge_file_info mfi; + struct diff_filespec o, a, b; + + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } + output(2, "Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + hashcpy(o.sha1, o_sha); + o.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + + mfi = merge_file(&o, &a, &b, + branch1, branch2); + + clean_merge = mfi.clean; + if (mfi.clean) + update_file(1, mfi.sha, mfi.mode, path); + else if (S_ISGITLINK(mfi.mode)) + output(1, "CONFLICT (submodule): Merge conflict in %s " + "- needs %s", path, sha1_to_hex(b.sha1)); + else { + output(1, "CONFLICT (%s): Merge conflict in %s", + reason, path); + + if (index_only) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* update_cache */, 1 /* update_working_directory */); + } + } else if (!o_sha && !a_sha && !b_sha) { + /* + * this entry was deleted altogether. a_mode == 0 means + * we had that path and want to actively remove it. + */ + remove_file(1, path, !a_mode); + } else + die("Fatal merge failure, shouldn't happen."); + + return clean_merge; +} + +int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) +{ + int code, clean; + + if (subtree_merge) { + merge = shift_tree_object(head, merge); + common = shift_tree_object(head, common); + } + + if (sha_eq(common->object.sha1, merge->object.sha1)) { + output(0, "Already uptodate!"); + *result = head; + return 1; + } + + code = git_merge_trees(index_only, common, head, merge); + + if (code != 0) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + if (unmerged_cache()) { + struct string_list *entries, *re_head, *re_merge; + int i; + string_list_clear(¤t_file_set, 1); + string_list_clear(¤t_directory_set, 1); + get_files_dirs(head); + get_files_dirs(merge); + + entries = get_unmerged(); + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + clean = process_renames(re_head, re_merge, + branch1, branch2); + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].string; + struct stage_data *e = entries->items[i].util; + if (!e->processed + && !process_entry(path, e, branch1, branch2)) + clean = 0; + } + + string_list_clear(re_merge, 0); + string_list_clear(re_head, 0); + string_list_clear(entries, 1); + + } + else + clean = 1; + + if (index_only) + *result = write_tree_from_memory(); + + return clean; +} + +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleanness of the merge. + */ +int merge_recursive(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + struct commit_list *ca, + struct commit **result) +{ + struct commit_list *iter; + struct commit *merged_common_ancestors; + struct tree *mrtree = mrtree; + int clean; + + if (show(4)) { + output(4, "Merging:"); + output_commit_title(h1); + output_commit_title(h2); + } + + if (!ca) { + ca = get_merge_bases(h1, h2, 1); + ca = reverse_commit_list(ca); + } + + if (show(5)) { + output(5, "found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + } + + merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } + + for (iter = ca; iter; iter = iter->next) { + call_depth++; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never actually used, as result of + * merge_trees has always overwritten it: the committed + * "conflicts" were already resolved. + */ + discard_cache(); + merge_recursive(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + NULL, + &merged_common_ancestors); + call_depth--; + + if (!merged_common_ancestors) + die("merge returned no commit"); + } + + discard_cache(); + if (!call_depth) { + read_cache(); + index_only = 0; + } else + index_only = 1; + + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, + branch1, branch2, &mrtree); + + if (index_only) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); + } + flush_output(); + return clean; +} + +int merge_recursive_config(const char *var, const char *value, void *cb) +{ + if (!strcasecmp(var, "merge.verbosity")) { + merge_recursive_verbosity = git_config_int(var, value); + return 0; + } + if (!strcasecmp(var, "diff.renamelimit")) { + diff_rename_limit = git_config_int(var, value); + return 0; + } + if (!strcasecmp(var, "merge.renamelimit")) { + merge_rename_limit = git_config_int(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +void merge_recursive_setup(int is_subtree_merge) +{ + if (getenv("GIT_MERGE_VERBOSITY")) + merge_recursive_verbosity = + strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); + if (merge_recursive_verbosity >= 5) + buffer_output = 0; + subtree_merge = is_subtree_merge; +} diff --git a/merge-recursive.h b/merge-recursive.h index f37630a8ad..73e4413a80 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -14,7 +14,11 @@ int merge_trees(struct tree *head, const char *branch1, const char *branch2, struct tree **result); - +struct commit *make_virtual_commit(struct tree *tree, const char *comment); +int merge_recursive_config(const char *var, const char *value, void *cb); +void merge_recursive_setup(int is_subtree_merge); struct tree *write_tree_from_memory(void); +extern int merge_recursive_verbosity; + #endif From 73118f89b81f5a3ed1bb56e2517627d56e9ebdfb Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Tue, 12 Aug 2008 22:13:59 +0200 Subject: [PATCH 02/11] merge-recursive.c: Add more generic merge_recursive_generic() merge_recursive_generic() takes, in comparison to to merge_recursive(), no commit ("struct commit *") arguments but SHA ids ("unsigned char *"), and no commit list of bases but an array of refs ("const char **"). This makes it more generic in the case that it can also take the SHA of a tree to merge trees without commits, for the bases, the head and the remote. merge_recursive_generic() also handles locking and updating of the index, which is a common use case of merge_recursive(). This patch also rewrites builtin-merge-recursive.c to make use of merge_recursive_generic(). By doing this, I stumbled over the limitation of 20 bases and I've added a warning if this limitation is exceeded. This patch qualifies make_virtual_commit() as static again because this function is not needed anymore outside merge-recursive.c. Signed-off-by: Stephan Beyer Signed-off-by: Junio C Hamano --- builtin-merge-recursive.c | 64 ++++++++++++--------------------------- merge-recursive.c | 53 ++++++++++++++++++++++++++++++++ merge-recursive.h | 4 ++- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 8bf2fa5df3..25f540b4a8 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -15,36 +15,13 @@ static const char *better_branch_name(const char *branch) return name ? name : branch; } -static struct commit *get_ref(const char *ref) -{ - unsigned char sha1[20]; - struct object *object; - - if (get_sha1(ref, sha1)) - die("Could not resolve ref '%s'", ref); - object = deref_tag(parse_object(sha1), ref, strlen(ref)); - if (!object) - return NULL; - if (object->type == OBJ_TREE) - return make_virtual_commit((struct tree*)object, - better_branch_name(ref)); - if (object->type != OBJ_COMMIT) - return NULL; - if (parse_commit((struct commit *)object)) - die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); - return (struct commit *)object; -} - int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - static const char *bases[20]; - static unsigned bases_count = 0; - int i, clean; + const char *bases[21]; + unsigned bases_count = 0; + int i, failed; const char *branch1, *branch2; - struct commit *result, *h1, *h2; - struct commit_list *ca = NULL; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; + unsigned char h1[20], h2[20]; int subtree_merge = 0; if (argv[0]) { @@ -60,10 +37,15 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) die("Usage: %s ... -- ...\n", argv[0]); for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--")) + if (!strcmp(argv[i], "--")) { + bases[bases_count] = NULL; break; - if (bases_count < sizeof(bases)/sizeof(*bases)) + } + if (bases_count < ARRAY_SIZE(bases)-1) bases[bases_count++] = argv[i]; + else + warning("Cannot handle more than %zu bases. " + "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); @@ -71,8 +53,10 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) branch1 = argv[++i]; branch2 = argv[++i]; - h1 = get_ref(branch1); - h2 = get_ref(branch2); + if (get_sha1(branch1, h1)) + die("Could not resolve ref '%s'", branch1); + if (get_sha1(branch2, h2)) + die("Could not resolve ref '%s'", branch2); branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); @@ -80,18 +64,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (merge_recursive_verbosity >= 3) printf("Merging %s with %s\n", branch1, branch2); - index_fd = hold_locked_index(lock, 1); - - for (i = 0; i < bases_count; i++) { - struct commit *ancestor = get_ref(bases[i]); - ca = commit_list_insert(ancestor, &ca); - } - clean = merge_recursive(h1, h2, branch1, branch2, ca, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); - - return clean ? 0: 1; + failed = merge_recursive_generic(bases, h1, branch1, h2, branch2); + if (failed < 0) + return 128; /* die() error code */ + return failed; } diff --git a/merge-recursive.c b/merge-recursive.c index a0cb3f47de..60d11716b3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1303,6 +1303,59 @@ int merge_recursive(struct commit *h1, return clean; } +static struct commit *get_ref(const unsigned char *sha1, const char *name) +{ + struct object *object; + + object = deref_tag(parse_object(sha1), name, strlen(name)); + if (!object) + return NULL; + if (object->type == OBJ_TREE) + return make_virtual_commit((struct tree*)object, name); + if (object->type != OBJ_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + return NULL; + return (struct commit *)object; +} + +int merge_recursive_generic(const char **base_list, + const unsigned char *head_sha1, const char *head_name, + const unsigned char *next_sha1, const char *next_name) +{ + int clean, index_fd; + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + struct commit *result; + struct commit *head_commit = get_ref(head_sha1, head_name); + struct commit *next_commit = get_ref(next_sha1, next_name); + struct commit_list *ca = NULL; + + if (base_list) { + int i; + for (i = 0; base_list[i]; ++i) { + unsigned char sha[20]; + struct commit *base; + if (get_sha1(base_list[i], sha)) + return error("Could not resolve ref '%s'", + base_list[i]); + if (!(base = get_ref(sha, base_list[i]))) + return error("Could not parse object '%s'", + base_list[i]); + commit_list_insert(base, &ca); + } + } + + index_fd = hold_locked_index(lock, 1); + clean = merge_recursive(head_commit, next_commit, + head_name, next_name, ca, &result); + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(lock))) + return error("Unable to write index."); + + return clean ? 0 : 1; +} + int merge_recursive_config(const char *var, const char *value, void *cb) { if (!strcasecmp(var, "merge.verbosity")) { diff --git a/merge-recursive.h b/merge-recursive.h index 73e4413a80..4dd6476af6 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -14,7 +14,9 @@ int merge_trees(struct tree *head, const char *branch1, const char *branch2, struct tree **result); -struct commit *make_virtual_commit(struct tree *tree, const char *comment); +extern int merge_recursive_generic(const char **base_list, + const unsigned char *head_sha1, const char *head_name, + const unsigned char *next_sha1, const char *next_name); int merge_recursive_config(const char *var, const char *value, void *cb); void merge_recursive_setup(int is_subtree_merge); struct tree *write_tree_from_memory(void); From 8a2fce1895c058945d8e2dbd8cb7456cc7450ad8 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 25 Aug 2008 16:25:57 +0200 Subject: [PATCH 03/11] merge-recursive: introduce merge_options This makes it possible to avoid passing the labels of branches as arguments to merge_recursive(), merge_trees() and merge_recursive_generic(). It also takes care of subtree merge, output buffering, verbosity, and rename limits - these were global variables till now in merge-recursive.c. A new function, named init_merge_options(), is introduced as well, it clears the struct merge_info, then initializes with default values, finally updates the default values based on the config and environment variables. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-checkout.c | 11 +- builtin-merge-recursive.c | 43 ++++--- merge-recursive.c | 265 +++++++++++++++++++------------------- merge-recursive.h | 42 ++++-- 4 files changed, 192 insertions(+), 169 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index b380ad6e80..f3c6b0fad2 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -264,6 +264,7 @@ static int merge_working_tree(struct checkout_opts *opts, */ struct tree *result; struct tree *work; + struct merge_options o; if (!opts->merge) return 1; parse_commit(old->commit); @@ -282,13 +283,17 @@ static int merge_working_tree(struct checkout_opts *opts, */ add_files_to_cache(NULL, NULL, 0); - work = write_tree_from_memory(); + init_merge_options(&o); + o.verbosity = 0; + work = write_tree_from_memory(&o); ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; - merge_trees(new->commit->tree, work, old->commit->tree, - new->name, "local", &result); + o.branch1 = new->name; + o.branch2 = "local"; + merge_trees(&o, new->commit->tree, work, + old->commit->tree, &result); ret = reset_tree(new->commit->tree, opts, 0); if (ret) return ret; diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 25f540b4a8..6b534c1a66 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -17,32 +17,33 @@ static const char *better_branch_name(const char *branch) int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - const char *bases[21]; + const unsigned char *bases[21]; unsigned bases_count = 0; int i, failed; - const char *branch1, *branch2; unsigned char h1[20], h2[20]; - int subtree_merge = 0; + struct merge_options o; + struct commit *result; + init_merge_options(&o); if (argv[0]) { int namelen = strlen(argv[0]); if (8 < namelen && !strcmp(argv[0] + namelen - 8, "-subtree")) - subtree_merge = 1; + o.subtree_merge = 1; } - git_config(merge_recursive_config, NULL); - merge_recursive_setup(subtree_merge); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--")) { - bases[bases_count] = NULL; + if (!strcmp(argv[i], "--")) break; + if (bases_count < ARRAY_SIZE(bases)-1) { + unsigned char *sha = xmalloc(20); + if (get_sha1(argv[i], sha)) + die("Could not parse object '%s'", argv[i]); + bases[bases_count++] = sha; } - if (bases_count < ARRAY_SIZE(bases)-1) - bases[bases_count++] = argv[i]; else warning("Cannot handle more than %zu bases. " "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]); @@ -50,21 +51,21 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); - branch1 = argv[++i]; - branch2 = argv[++i]; + o.branch1 = argv[++i]; + o.branch2 = argv[++i]; - if (get_sha1(branch1, h1)) - die("Could not resolve ref '%s'", branch1); - if (get_sha1(branch2, h2)) - die("Could not resolve ref '%s'", branch2); + if (get_sha1(o.branch1, h1)) + die("Could not resolve ref '%s'", o.branch1); + if (get_sha1(o.branch2, h2)) + die("Could not resolve ref '%s'", o.branch2); - branch1 = better_branch_name(branch1); - branch2 = better_branch_name(branch2); + o.branch1 = better_branch_name(o.branch1); + o.branch2 = better_branch_name(o.branch2); - if (merge_recursive_verbosity >= 3) - printf("Merging %s with %s\n", branch1, branch2); + if (o.verbosity >= 3) + printf("Merging %s with %s\n", o.branch1, o.branch2); - failed = merge_recursive_generic(bases, h1, branch1, h2, branch2); + failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/merge-recursive.c b/merge-recursive.c index 60d11716b3..457ad845f5 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -20,8 +20,6 @@ #include "attr.h" #include "merge-recursive.h" -static int subtree_merge; - static struct tree *shift_tree_object(struct tree *one, struct tree *two) { unsigned char shifted[20]; @@ -83,16 +81,11 @@ static struct string_list current_file_set = {NULL, 0, 0, 1}; static struct string_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; -int merge_recursive_verbosity = 2; -static int diff_rename_limit = -1; -static int merge_rename_limit = -1; -static int buffer_output = 1; static struct strbuf obuf = STRBUF_INIT; -static int show(int v) +static int show(struct merge_options *o, int v) { - return (!call_depth && merge_recursive_verbosity >= v) || - merge_recursive_verbosity >= 5; + return (!call_depth && o->verbosity >= v) || o->verbosity >= 5; } static void flush_output(void) @@ -103,12 +96,12 @@ static void flush_output(void) } } -static void output(int v, const char *fmt, ...) +static void output(struct merge_options *o, int v, const char *fmt, ...) { int len; va_list ap; - if (!show(v)) + if (!show(o, v)) return; strbuf_grow(&obuf, call_depth * 2 + 2); @@ -132,7 +125,7 @@ static void output(int v, const char *fmt, ...) } strbuf_setlen(&obuf, obuf.len + len); strbuf_add(&obuf, "\n", 1); - if (!buffer_output) + if (!o->buffer_output) flush_output(); } @@ -219,17 +212,17 @@ static int git_merge_trees(int index_only, return rc; } -struct tree *write_tree_from_memory(void) +struct tree *write_tree_from_memory(struct merge_options *o) { struct tree *result = NULL; if (unmerged_cache()) { int i; - output(0, "There are unmerged index entries:"); + output(o, 0, "There are unmerged index entries:"); for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) - output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); + output(o, 0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); } return NULL; } @@ -341,11 +334,12 @@ struct rename * 'b_tree') to be able to associate the correct cache entries with * the rename information. 'tree' is always equal to either a_tree or b_tree. */ -static struct string_list *get_renames(struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) +static struct string_list *get_renames(struct merge_options *o, + struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries) { int i; struct string_list *renames; @@ -355,8 +349,8 @@ static struct string_list *get_renames(struct tree *tree, diff_setup(&opts); DIFF_OPT_SET(&opts, RECURSIVE); opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : - diff_rename_limit >= 0 ? diff_rename_limit : + opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : + o->diff_rename_limit >= 0 ? o->diff_rename_limit : 500; opts.warn_on_too_large_rename = 1; opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -717,7 +711,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o, return result; } -static void conflict_rename_rename(struct rename *ren1, +static void conflict_rename_rename(struct merge_options *o, + struct rename *ren1, const char *branch1, struct rename *ren2, const char *branch2) @@ -730,13 +725,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (string_list_has_string(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s adding as %s instead", + output(o, 1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (string_list_has_string(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s adding as %s instead", + output(o, 1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -757,24 +752,26 @@ static void conflict_rename_rename(struct rename *ren1, free(del[delp]); } -static void conflict_rename_dir(struct rename *ren1, +static void conflict_rename_dir(struct merge_options *o, + struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); + output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); } -static void conflict_rename_rename_2(struct rename *ren1, +static void conflict_rename_rename_2(struct merge_options *o, + struct rename *ren1, const char *branch1, struct rename *ren2, const char *branch2) { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renaming %s to %s and %s to %s instead", + output(o, 1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path, 0); @@ -784,10 +781,9 @@ static void conflict_rename_rename_2(struct rename *ren1, free(new_path1); } -static int process_renames(struct string_list *a_renames, - struct string_list *b_renames, - const char *a_branch, - const char *b_branch) +static int process_renames(struct merge_options *o, + struct string_list *a_renames, + struct string_list *b_renames) { int clean_merge = 1, i, j; struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; @@ -832,15 +828,15 @@ static int process_renames(struct string_list *a_renames, renames1 = a_renames; renames2 = b_renames; renames2Dst = &b_by_dst; - branch1 = a_branch; - branch2 = b_branch; + branch1 = o->branch1; + branch2 = o->branch2; } else { struct rename *tmp; renames1 = b_renames; renames2 = a_renames; renames2Dst = &a_by_dst; - branch1 = b_branch; - branch2 = a_branch; + branch1 = o->branch2; + branch2 = o->branch1; tmp = ren2; ren2 = ren1; ren1 = tmp; @@ -867,7 +863,7 @@ static int process_renames(struct string_list *a_renames, ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { clean_merge = 0; - output(1, "CONFLICT (rename/rename): " + output(o, 1, "CONFLICT (rename/rename): " "Rename \"%s\"->\"%s\" in branch \"%s\" " "rename \"%s\"->\"%s\" in \"%s\"%s", src, ren1_dst, branch1, @@ -878,7 +874,7 @@ static int process_renames(struct string_list *a_renames, update_file(0, ren1->pair->one->sha1, ren1->pair->one->mode, src); } - conflict_rename_rename(ren1, branch1, ren2, branch2); + conflict_rename_rename(o, ren1, branch1, ren2, branch2); } else { struct merge_file_info mfi; remove_file(1, ren1_src, 1); @@ -888,13 +884,13 @@ static int process_renames(struct string_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); + output(o, 1, "Renaming %s->%s", src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(o, 2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output(1, "CONFLICT (content): merge conflict in %s", + output(o, 1, "CONFLICT (content): merge conflict in %s", ren1_dst); clean_merge = 0; @@ -925,14 +921,14 @@ static int process_renames(struct string_list *a_renames, if (string_list_has_string(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); - conflict_rename_dir(ren1, branch1); + conflict_rename_dir(o, ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { clean_merge = 0; - output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " + output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -941,31 +937,32 @@ static int process_renames(struct string_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output(1, "Adding as %s instead", new_path); + output(o, 1, "Adding as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " + output(o, 1, "CONFLICT (rename/rename): " + "Rename %s->%s in %s. " "Rename %s->%s in %s", ren1_src, ren1_dst, branch1, ren2->pair->one->path, ren2->pair->two->path, branch2); - conflict_rename_rename_2(ren1, branch1, ren2, branch2); + conflict_rename_rename_2(o, ren1, branch1, ren2, branch2); } else try_merge = 1; if (try_merge) { - struct diff_filespec *o, *a, *b; + struct diff_filespec *one, *a, *b; struct merge_file_info mfi; src_other.path = (char *)ren1_src; - o = ren1->pair->one; + one = ren1->pair->one; if (a_renames == renames1) { a = ren1->pair->two; b = &src_other; @@ -973,8 +970,8 @@ static int process_renames(struct string_list *a_renames, b = ren1->pair->two; a = &src_other; } - mfi = merge_file(o, a, b, - a_branch, b_branch); + mfi = merge_file(one, a, b, + o->branch1, o->branch2); if (mfi.clean && sha_eq(mfi.sha, ren1->pair->two->sha1) && @@ -984,20 +981,20 @@ static int process_renames(struct string_list *a_renames, * t6022 test. If you change * it update the test too. */ - output(3, "Skipped %s (merged same as existing)", ren1_dst); + output(o, 3, "Skipped %s (merged same as existing)", ren1_dst); else { if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); + output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(o, 2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output(1, "CONFLICT (rename/modify): Merge conflict in %s", + output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); clean_merge = 0; if (!index_only) update_stages(ren1_dst, - o, a, b, 1); + one, a, b, 1); } update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); } @@ -1016,9 +1013,8 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) } /* Per entry merge function */ -static int process_entry(const char *path, struct stage_data *entry, - const char *branch1, - const char *branch2) +static int process_entry(struct merge_options *o, + const char *path, struct stage_data *entry) { /* printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); @@ -1040,23 +1036,23 @@ static int process_entry(const char *path, struct stage_data *entry, /* Deleted in both or deleted in one and * unchanged in the other */ if (a_sha) - output(2, "Removing %s", path); + output(o, 2, "Removing %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { /* Deleted in one and changed in the other */ clean_merge = 0; if (!a_sha) { - output(1, "CONFLICT (delete/modify): %s deleted in %s " + output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch1, - branch2, branch2, path); + path, o->branch1, + o->branch2, o->branch2, path); update_file(0, b_sha, b_mode, path); } else { - output(1, "CONFLICT (delete/modify): %s deleted in %s " + output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch2, - branch1, branch1, path); + path, o->branch2, + o->branch1, o->branch1, path); update_file(0, a_sha, a_mode, path); } } @@ -1071,14 +1067,14 @@ static int process_entry(const char *path, struct stage_data *entry, const char *conf; if (a_sha) { - add_branch = branch1; - other_branch = branch2; + add_branch = o->branch1; + other_branch = o->branch2; mode = a_mode; sha = a_sha; conf = "file/directory"; } else { - add_branch = branch2; - other_branch = branch1; + add_branch = o->branch2; + other_branch = o->branch1; mode = b_mode; sha = b_sha; conf = "directory/file"; @@ -1086,13 +1082,13 @@ static int process_entry(const char *path, struct stage_data *entry, if (string_list_has_string(¤t_directory_set, path)) { const char *new_path = unique_path(path, add_branch); clean_merge = 0; - output(1, "CONFLICT (%s): There is a directory with name %s in %s. " + output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output(2, "Adding %s", path); + output(o, 2, "Adding %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -1100,32 +1096,32 @@ static int process_entry(const char *path, struct stage_data *entry, /* case D: Modified in both, but differently. */ const char *reason = "content"; struct merge_file_info mfi; - struct diff_filespec o, a, b; + struct diff_filespec one, a, b; if (!o_sha) { reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output(2, "Auto-merging %s", path); - o.path = a.path = b.path = (char *)path; - hashcpy(o.sha1, o_sha); - o.mode = o_mode; + output(o, 2, "Auto-merging %s", path); + one.path = a.path = b.path = (char *)path; + hashcpy(one.sha1, o_sha); + one.mode = o_mode; hashcpy(a.sha1, a_sha); a.mode = a_mode; hashcpy(b.sha1, b_sha); b.mode = b_mode; - mfi = merge_file(&o, &a, &b, - branch1, branch2); + mfi = merge_file(&one, &a, &b, + o->branch1, o->branch2); clean_merge = mfi.clean; if (mfi.clean) update_file(1, mfi.sha, mfi.mode, path); else if (S_ISGITLINK(mfi.mode)) - output(1, "CONFLICT (submodule): Merge conflict in %s " + output(o, 1, "CONFLICT (submodule): Merge conflict in %s " "- needs %s", path, sha1_to_hex(b.sha1)); else { - output(1, "CONFLICT (%s): Merge conflict in %s", + output(o, 1, "CONFLICT (%s): Merge conflict in %s", reason, path); if (index_only) @@ -1146,22 +1142,21 @@ static int process_entry(const char *path, struct stage_data *entry, return clean_merge; } -int merge_trees(struct tree *head, +int merge_trees(struct merge_options *o, + struct tree *head, struct tree *merge, struct tree *common, - const char *branch1, - const char *branch2, struct tree **result) { int code, clean; - if (subtree_merge) { + if (o->subtree_merge) { merge = shift_tree_object(head, merge); common = shift_tree_object(head, common); } if (sha_eq(common->object.sha1, merge->object.sha1)) { - output(0, "Already uptodate!"); + output(o, 0, "Already uptodate!"); *result = head; return 1; } @@ -1182,15 +1177,14 @@ int merge_trees(struct tree *head, get_files_dirs(merge); entries = get_unmerged(); - re_head = get_renames(head, common, head, merge, entries); - re_merge = get_renames(merge, common, head, merge, entries); - clean = process_renames(re_head, re_merge, - branch1, branch2); + re_head = get_renames(o, head, common, head, merge, entries); + re_merge = get_renames(o, merge, common, head, merge, entries); + clean = process_renames(o, re_head, re_merge); for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].string; struct stage_data *e = entries->items[i].util; if (!e->processed - && !process_entry(path, e, branch1, branch2)) + && !process_entry(o, path, e)) clean = 0; } @@ -1203,7 +1197,7 @@ int merge_trees(struct tree *head, clean = 1; if (index_only) - *result = write_tree_from_memory(); + *result = write_tree_from_memory(o); return clean; } @@ -1223,10 +1217,9 @@ static struct commit_list *reverse_commit_list(struct commit_list *list) * Merge the commits h1 and h2, return the resulting virtual * commit object and a flag indicating the cleanness of the merge. */ -int merge_recursive(struct commit *h1, +int merge_recursive(struct merge_options *o, + struct commit *h1, struct commit *h2, - const char *branch1, - const char *branch2, struct commit_list *ca, struct commit **result) { @@ -1235,8 +1228,8 @@ int merge_recursive(struct commit *h1, struct tree *mrtree = mrtree; int clean; - if (show(4)) { - output(4, "Merging:"); + if (show(o, 4)) { + output(o, 4, "Merging:"); output_commit_title(h1); output_commit_title(h2); } @@ -1246,8 +1239,8 @@ int merge_recursive(struct commit *h1, ca = reverse_commit_list(ca); } - if (show(5)) { - output(5, "found %u common ancestor(s):", commit_list_count(ca)); + if (show(o, 5)) { + output(o, 5, "found %u common ancestor(s):", commit_list_count(ca)); for (iter = ca; iter; iter = iter->next) output_commit_title(iter->item); } @@ -1264,6 +1257,7 @@ int merge_recursive(struct commit *h1, } for (iter = ca; iter; iter = iter->next) { + const char *saved_b1, *saved_b2; call_depth++; /* * When the merge fails, the result contains files @@ -1273,11 +1267,14 @@ int merge_recursive(struct commit *h1, * "conflicts" were already resolved. */ discard_cache(); - merge_recursive(merged_common_ancestors, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - NULL, - &merged_common_ancestors); + saved_b1 = o->branch1; + saved_b2 = o->branch2; + o->branch1 = "Temporary merge branch 1"; + o->branch2 = "Temporary merge branch 2"; + merge_recursive(o, merged_common_ancestors, iter->item, + NULL, &merged_common_ancestors); + o->branch1 = saved_b1; + o->branch2 = saved_b2; call_depth--; if (!merged_common_ancestors) @@ -1291,8 +1288,8 @@ int merge_recursive(struct commit *h1, } else index_only = 1; - clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, - branch1, branch2, &mrtree); + clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, + &mrtree); if (index_only) { *result = make_virtual_commit(mrtree, "merged tree"); @@ -1319,35 +1316,33 @@ static struct commit *get_ref(const unsigned char *sha1, const char *name) return (struct commit *)object; } -int merge_recursive_generic(const char **base_list, - const unsigned char *head_sha1, const char *head_name, - const unsigned char *next_sha1, const char *next_name) +int merge_recursive_generic(struct merge_options *o, + const unsigned char *head, + const unsigned char *merge, + int num_base_list, + const unsigned char **base_list, + struct commit **result) { int clean, index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - struct commit *result; - struct commit *head_commit = get_ref(head_sha1, head_name); - struct commit *next_commit = get_ref(next_sha1, next_name); + struct commit *head_commit = get_ref(head, o->branch1); + struct commit *next_commit = get_ref(merge, o->branch2); struct commit_list *ca = NULL; if (base_list) { int i; - for (i = 0; base_list[i]; ++i) { - unsigned char sha[20]; + for (i = 0; i < num_base_list; ++i) { struct commit *base; - if (get_sha1(base_list[i], sha)) - return error("Could not resolve ref '%s'", - base_list[i]); - if (!(base = get_ref(sha, base_list[i]))) + if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i])))) return error("Could not parse object '%s'", - base_list[i]); + sha1_to_hex(base_list[i])); commit_list_insert(base, &ca); } } index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(head_commit, next_commit, - head_name, next_name, ca, &result); + clean = merge_recursive(o, head_commit, next_commit, ca, + result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) @@ -1356,29 +1351,35 @@ int merge_recursive_generic(const char **base_list, return clean ? 0 : 1; } -int merge_recursive_config(const char *var, const char *value, void *cb) +static int merge_recursive_config(const char *var, const char *value, void *cb) { + struct merge_options *o = cb; if (!strcasecmp(var, "merge.verbosity")) { - merge_recursive_verbosity = git_config_int(var, value); + o->verbosity = git_config_int(var, value); return 0; } if (!strcasecmp(var, "diff.renamelimit")) { - diff_rename_limit = git_config_int(var, value); + o->diff_rename_limit = git_config_int(var, value); return 0; } if (!strcasecmp(var, "merge.renamelimit")) { - merge_rename_limit = git_config_int(var, value); + o->merge_rename_limit = git_config_int(var, value); return 0; } return git_default_config(var, value, cb); } -void merge_recursive_setup(int is_subtree_merge) +void init_merge_options(struct merge_options *o) { + memset(o, 0, sizeof(struct merge_options)); + o->verbosity = 2; + o->buffer_output = 1; + o->diff_rename_limit = -1; + o->merge_rename_limit = -1; + git_config(merge_recursive_config, o); if (getenv("GIT_MERGE_VERBOSITY")) - merge_recursive_verbosity = + o->verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); - if (merge_recursive_verbosity >= 5) - buffer_output = 0; - subtree_merge = is_subtree_merge; + if (o->verbosity >= 5) + o->buffer_output = 0; } diff --git a/merge-recursive.h b/merge-recursive.h index 4dd6476af6..72f0a2895d 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -1,26 +1,42 @@ #ifndef MERGE_RECURSIVE_H #define MERGE_RECURSIVE_H -int merge_recursive(struct commit *h1, +struct merge_options { + const char *branch1; + const char *branch2; + unsigned subtree_merge : 1; + unsigned buffer_output : 1; + int verbosity; + int diff_rename_limit; + int merge_rename_limit; +}; + +/* merge_trees() but with recursive ancestor consolidation */ +int merge_recursive(struct merge_options *o, + struct commit *h1, struct commit *h2, - const char *branch1, - const char *branch2, struct commit_list *ancestors, struct commit **result); -int merge_trees(struct tree *head, +/* rename-detecting three-way merge, no recursion */ +int merge_trees(struct merge_options *o, + struct tree *head, struct tree *merge, struct tree *common, - const char *branch1, - const char *branch2, struct tree **result); -extern int merge_recursive_generic(const char **base_list, - const unsigned char *head_sha1, const char *head_name, - const unsigned char *next_sha1, const char *next_name); -int merge_recursive_config(const char *var, const char *value, void *cb); -void merge_recursive_setup(int is_subtree_merge); -struct tree *write_tree_from_memory(void); -extern int merge_recursive_verbosity; +/* + * "git-merge-recursive" can be fed trees; wrap them into + * virtual commits and call merge_recursive() proper. + */ +int merge_recursive_generic(struct merge_options *o, + const unsigned char *head, + const unsigned char *merge, + int num_ca, + const unsigned char **ca, + struct commit **result); + +void init_merge_options(struct merge_options *o); +struct tree *write_tree_from_memory(struct merge_options *o); #endif From 18668f5319b079cce29e19817bc352b1413e0908 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 28 Aug 2008 15:43:00 +0200 Subject: [PATCH 04/11] builtin-merge: avoid run_command_v_opt() for recursive and subtree The try_merge_strategy() function always ran the strategy in a separate process, though this is not always necessary. The recursive and subtree strategy can be called without a fork(). This patch adds a check, and calls recursive in the same process without wasting resources. Signed-off-by: Johannes Schindelin Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-merge.c | 92 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index 9ad9791068..b857cf6246 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -23,6 +23,7 @@ #include "color.h" #include "rerere.h" #include "help.h" +#include "merge-recursive.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -545,28 +546,64 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *j; struct strbuf buf; - args = xmalloc((4 + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_init(&buf, 0); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - return -ret; + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + int clean; + struct commit *result; + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int index_fd; + struct commit_list *reversed = NULL; + struct merge_options o; + + if (remoteheads->next) { + error("Not handling anything other than two heads merge."); + return 2; + } + + init_merge_options(&o); + if (!strcmp(strategy, "subtree")) + o.subtree_merge = 1; + + o.branch1 = head_arg; + o.branch2 = remoteheads->item->util; + + for (j = common; j; j = j->next) + commit_list_insert(j->item, &reversed); + + index_fd = hold_locked_index(lock, 1); + clean = merge_recursive(&o, lookup_commit(head), + remoteheads->item, reversed, &result); + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(lock))) + die ("unable to write %s", get_index_file()); + return clean ? 0 : 1; + } else { + args = xmalloc((4 + commit_list_count(common) + + commit_list_count(remoteheads)) * sizeof(char *)); + strbuf_init(&buf, 0); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (j = common; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remoteheads; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remoteheads; j; j = j->next) + free((void *)args[i++]); + free(args); + discard_cache(); + if (read_cache() < 0) + die("failed to read the cache"); + return -ret; + } } static void count_diff_files(struct diff_queue_struct *q, @@ -777,10 +814,6 @@ static int evaluate_result(void) int cnt = 0; struct rev_info rev; - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - /* Check how many files differ. */ init_revisions(&rev, ""); setup_revisions(0, NULL, &rev, NULL); @@ -914,12 +947,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { struct object *o; + struct commit *commit; o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); if (!o) die("%s - not something we can merge", argv[i]); - remotes = &commit_list_insert(lookup_commit(o->sha1), - remotes)->next; + commit = lookup_commit(o->sha1); + commit->util = (void *)argv[i]; + remotes = &commit_list_insert(commit, remotes)->next; strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); setenv(buf.buf, argv[i], 1); @@ -1113,7 +1148,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } /* Automerge succeeded. */ - discard_cache(); write_tree_trivial(result_tree); automerge_was_ok = 1; break; From 6eb1b437933e075bd84c5cdcf5de95ed4db4820f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 2 Sep 2008 14:11:15 -0700 Subject: [PATCH 05/11] cherry-pick/revert: make direct internal call to merge_tree() Refactored merge-recursive interface may still not be ideal but it already allows us to make a direct call to merge_tree(). One regression is that the status message is lost as there is no way to flush them from outside the refactored library code yet. [jc: initial version by Miklos, with moderate amount of fixup by me] Signed-off-by: Junio C Hamano --- builtin-revert.c | 83 +++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 36677053f8..8486539740 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -12,6 +12,7 @@ #include "diff.h" #include "revision.h" #include "rerere.h" +#include "merge-recursive.h" /* * This implements the builtins revert and cherry-pick. @@ -201,36 +202,6 @@ static void set_author_ident_env(const char *message) sha1_to_hex(commit->object.sha1)); } -static int merge_recursive(const char *base_sha1, - const char *head_sha1, const char *head_name, - const char *next_sha1, const char *next_name) -{ - char buffer[256]; - const char *argv[6]; - int i = 0; - - sprintf(buffer, "GITHEAD_%s", head_sha1); - setenv(buffer, head_name, 1); - sprintf(buffer, "GITHEAD_%s", next_sha1); - setenv(buffer, next_name, 1); - - /* - * This three way merge is an interesting one. We are at - * $head, and would want to apply the change between $commit - * and $prev on top of us (when reverting), or the change between - * $prev and $commit on top of us (when cherry-picking or replaying). - */ - argv[i++] = "merge-recursive"; - if (base_sha1) - argv[i++] = base_sha1; - argv[i++] = "--"; - argv[i++] = head_sha1; - argv[i++] = next_sha1; - argv[i++] = NULL; - - return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD); -} - static char *help_msg(const unsigned char *sha1) { static char helpbuf[1024]; @@ -263,14 +234,27 @@ static int index_is_dirty(void) return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); } +static struct tree *empty_tree(void) +{ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); + return tree; +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; struct commit *base, *next, *parent; - int i; + int i, index_fd, clean; char *oneline, *reencoded_message = NULL; const char *message, *encoding; const char *defmsg = xstrdup(git_path("MERGE_MSG")); + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + static struct lock_file index_lock; git_config(git_default_config, NULL); me = action == REVERT ? "revert" : "cherry-pick"; @@ -281,6 +265,8 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (action == REVERT && !no_replay) die("revert is incompatible with replay"); + if (read_cache() < 0) + die("git %s: failed to read the index", me); if (no_commit) { /* * We do not intend to commit immediately. We just want to @@ -293,12 +279,12 @@ static int revert_or_cherry_pick(int argc, const char **argv) } else { if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); - if (read_cache() < 0) - die("could not read the index"); if (index_is_dirty()) die ("Dirty index: cannot %s", me); - discard_cache(); } + discard_cache(); + + index_fd = hold_locked_index(&index_lock, 1); if (!commit->parents) { if (action == REVERT) @@ -332,6 +318,10 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Cannot get commit message for %s", sha1_to_hex(commit->object.sha1)); + if (parent && parse_commit(parent) < 0) + die("%s: cannot parse parent commit %s", + me, sha1_to_hex(parent->object.sha1)); + /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -374,13 +364,26 @@ static int revert_or_cherry_pick(int argc, const char **argv) } } - if (merge_recursive(base == NULL ? - NULL : sha1_to_hex(base->object.sha1), - sha1_to_hex(head), "HEAD", - sha1_to_hex(next->object.sha1), oneline) || - write_cache_as_tree(head, 0, NULL)) { + read_cache(); + init_merge_options(&o); + o.branch1 = "HEAD"; + o.branch2 = oneline; + + head_tree = parse_tree_indirect(head); + next_tree = next ? next->tree : empty_tree(); + base_tree = base ? base->tree : empty_tree(); + + clean = merge_trees(&o, + head_tree, + next_tree, base_tree, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(&index_lock))) + die("%s: Unable to write new index file", me); + + if (!clean) { add_to_msg("\nConflicts:\n\n"); - read_cache(); for (i = 0; i < active_nr;) { struct cache_entry *ce = active_cache[i++]; if (ce_stage(ce)) { From 5033639c951da27fc4d4becbc3e46dc05096ce63 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 2 Sep 2008 23:30:09 +0200 Subject: [PATCH 06/11] merge-recursive: move call_depth to struct merge_options Signed-off-by: Miklos Vajna --- merge-recursive.c | 25 ++++++++++++------------- merge-recursive.h | 1 + 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 457ad845f5..5bb20aa8ba 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -80,12 +80,11 @@ struct stage_data static struct string_list current_file_set = {NULL, 0, 0, 1}; static struct string_list current_directory_set = {NULL, 0, 0, 1}; -static int call_depth = 0; static struct strbuf obuf = STRBUF_INIT; static int show(struct merge_options *o, int v) { - return (!call_depth && o->verbosity >= v) || o->verbosity >= 5; + return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5; } static void flush_output(void) @@ -104,9 +103,9 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) if (!show(o, v)) return; - strbuf_grow(&obuf, call_depth * 2 + 2); - memset(obuf.buf + obuf.len, ' ', call_depth * 2); - strbuf_setlen(&obuf, obuf.len + call_depth * 2); + strbuf_grow(&obuf, o->call_depth * 2 + 2); + memset(obuf.buf + obuf.len, ' ', o->call_depth * 2); + strbuf_setlen(&obuf, obuf.len + o->call_depth * 2); va_start(ap, fmt); len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); @@ -129,11 +128,11 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) flush_output(); } -static void output_commit_title(struct commit *commit) +static void output_commit_title(struct merge_options *o, struct commit *commit) { int i; flush_output(); - for (i = call_depth; i--;) + for (i = o->call_depth; i--;) fputs(" ", stdout); if (commit->util) printf("virtual %s\n", (char *)commit->util); @@ -1230,8 +1229,8 @@ int merge_recursive(struct merge_options *o, if (show(o, 4)) { output(o, 4, "Merging:"); - output_commit_title(h1); - output_commit_title(h2); + output_commit_title(o, h1); + output_commit_title(o, h2); } if (!ca) { @@ -1242,7 +1241,7 @@ int merge_recursive(struct merge_options *o, if (show(o, 5)) { output(o, 5, "found %u common ancestor(s):", commit_list_count(ca)); for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); + output_commit_title(o, iter->item); } merged_common_ancestors = pop_commit(&ca); @@ -1258,7 +1257,7 @@ int merge_recursive(struct merge_options *o, for (iter = ca; iter; iter = iter->next) { const char *saved_b1, *saved_b2; - call_depth++; + o->call_depth++; /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is @@ -1275,14 +1274,14 @@ int merge_recursive(struct merge_options *o, NULL, &merged_common_ancestors); o->branch1 = saved_b1; o->branch2 = saved_b2; - call_depth--; + o->call_depth--; if (!merged_common_ancestors) die("merge returned no commit"); } discard_cache(); - if (!call_depth) { + if (!o->call_depth) { read_cache(); index_only = 0; } else diff --git a/merge-recursive.h b/merge-recursive.h index 72f0a2895d..4f5537417e 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -9,6 +9,7 @@ struct merge_options { int verbosity; int diff_rename_limit; int merge_rename_limit; + int call_depth; }; /* merge_trees() but with recursive ancestor consolidation */ From b7fa51da9be3cee9e2ca31af4edfec18b8ecbce7 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 2 Sep 2008 23:53:47 +0200 Subject: [PATCH 07/11] merge-recursive: get rid of the index_only global variable struct merge_options already has a call_depth member, and index_only global variable always equals to !!call_depth. We always use index_only as a condition, so we can just use call_depth instead of index_only. Signed-off-by: Miklos Vajna --- merge-recursive.c | 140 ++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 73 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 5bb20aa8ba..c426589d11 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -165,17 +165,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return add_cache_entry(ce, options); } -/* - * This is a global variable which is used in a number of places but - * only written to in the 'merge' function. - * - * index_only == 1 => Don't leave any non-stage 0 entries in the cache and - * don't update the working directory. - * 0 => Leave unmerged entries in the cache and update - * the working directory. - */ -static int index_only = 0; - static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) { parse_tree(tree); @@ -428,10 +417,11 @@ static int remove_path(const char *name) return ret; } -static int remove_file(int clean, const char *path, int no_wd) +static int remove_file(struct merge_options *o, int clean, + const char *path, int no_wd) { - int update_cache = index_only || clean; - int update_working_directory = !index_only && !no_wd; + int update_cache = o->call_depth || clean; + int update_working_directory = !o->call_depth && !no_wd; if (update_cache) { if (remove_file_from_cache(path)) @@ -509,13 +499,14 @@ static int make_room_for_path(const char *path) return error(msg, path, ": perhaps a D/F conflict?"); } -static void update_file_flags(const unsigned char *sha, +static void update_file_flags(struct merge_options *o, + const unsigned char *sha, unsigned mode, const char *path, int update_cache, int update_wd) { - if (index_only) + if (o->call_depth) update_wd = 0; if (update_wd) { @@ -574,12 +565,13 @@ static void update_file_flags(const unsigned char *sha, add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } -static void update_file(int clean, +static void update_file(struct merge_options *o, + int clean, const unsigned char *sha, unsigned mode, const char *path) { - update_file_flags(sha, mode, path, index_only || clean, !index_only); + update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth); } /* Low level file merging, update and removal */ @@ -609,8 +601,9 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm) mm->size = size; } -static int merge_3way(mmbuffer_t *result_buf, - struct diff_filespec *o, +static int merge_3way(struct merge_options *o, + mmbuffer_t *result_buf, + struct diff_filespec *one, struct diff_filespec *a, struct diff_filespec *b, const char *branch1, @@ -623,13 +616,13 @@ static int merge_3way(mmbuffer_t *result_buf, name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); - fill_mm(o->sha1, &orig); + fill_mm(one->sha1, &orig); fill_mm(a->sha1, &src1); fill_mm(b->sha1, &src2); merge_status = ll_merge(result_buf, a->path, &orig, &src1, name1, &src2, name2, - index_only); + o->call_depth); free(name1); free(name2); @@ -639,9 +632,12 @@ static int merge_3way(mmbuffer_t *result_buf, return merge_status; } -static struct merge_file_info merge_file(struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - const char *branch1, const char *branch2) +static struct merge_file_info merge_file(struct merge_options *o, + struct diff_filespec *one, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) { struct merge_file_info result; result.merge = 0; @@ -657,31 +653,31 @@ static struct merge_file_info merge_file(struct diff_filespec *o, hashcpy(result.sha, b->sha1); } } else { - if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + if (!sha_eq(a->sha1, one->sha1) && !sha_eq(b->sha1, one->sha1)) result.merge = 1; /* * Merge modes */ - if (a->mode == b->mode || a->mode == o->mode) + if (a->mode == b->mode || a->mode == one->mode) result.mode = b->mode; else { result.mode = a->mode; - if (b->mode != o->mode) { + if (b->mode != one->mode) { result.clean = 0; result.merge = 1; } } - if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) + if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1)) hashcpy(result.sha, b->sha1); - else if (sha_eq(b->sha1, o->sha1)) + else if (sha_eq(b->sha1, one->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { mmbuffer_t result_buf; int merge_status; - merge_status = merge_3way(&result_buf, o, a, b, + merge_status = merge_3way(o, &result_buf, one, a, b, branch1, branch2); if ((merge_status < 0) || !result_buf.ptr) @@ -726,22 +722,22 @@ static void conflict_rename_rename(struct merge_options *o, dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); output(o, 1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); - remove_file(0, ren1_dst, 0); + remove_file(o, 0, ren1_dst, 0); } if (string_list_has_string(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); output(o, 1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); - remove_file(0, ren2_dst, 0); + remove_file(o, 0, ren2_dst, 0); } - if (index_only) { + if (o->call_depth) { remove_file_from_cache(dst_name1); remove_file_from_cache(dst_name2); /* * Uncomment to leave the conflicting names in the resulting tree * - * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); - * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); + * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); + * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); */ } else { update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); @@ -757,8 +753,8 @@ static void conflict_rename_dir(struct merge_options *o, { char *new_path = unique_path(ren1->pair->two->path, branch1); output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + remove_file(o, 0, ren1->pair->two->path, 0); + update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); } @@ -773,9 +769,9 @@ static void conflict_rename_rename_2(struct merge_options *o, output(o, 1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); - update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + remove_file(o, 0, ren1->pair->two->path, 0); + update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); free(new_path2); free(new_path1); } @@ -867,17 +863,18 @@ static int process_renames(struct merge_options *o, "rename \"%s\"->\"%s\" in \"%s\"%s", src, ren1_dst, branch1, src, ren2_dst, branch2, - index_only ? " (left unresolved)": ""); - if (index_only) { + o->call_depth ? " (left unresolved)": ""); + if (o->call_depth) { remove_file_from_cache(src); - update_file(0, ren1->pair->one->sha1, + update_file(o, 0, ren1->pair->one->sha1, ren1->pair->one->mode, src); } conflict_rename_rename(o, ren1, branch1, ren2, branch2); } else { struct merge_file_info mfi; - remove_file(1, ren1_src, 1); - mfi = merge_file(ren1->pair->one, + remove_file(o, 1, ren1_src, 1); + mfi = merge_file(o, + ren1->pair->one, ren1->pair->two, ren2->pair->two, branch1, @@ -893,14 +890,14 @@ static int process_renames(struct merge_options *o, ren1_dst); clean_merge = 0; - if (!index_only) + if (!o->call_depth) update_stages(ren1_dst, ren1->pair->one, ren1->pair->two, ren2->pair->two, 1 /* clear */); } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst); } } else { /* Renamed in 1, maybe changed in 2 */ @@ -909,7 +906,7 @@ static int process_renames(struct merge_options *o, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, index_only || stage == 3); + remove_file(o, 1, ren1_src, o->call_depth || stage == 3); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -931,7 +928,7 @@ static int process_renames(struct merge_options *o, "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); } else if (!sha_eq(dst_other.sha1, null_sha1)) { const char *new_path; clean_merge = 0; @@ -942,7 +939,7 @@ static int process_renames(struct merge_options *o, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); output(o, 1, "Adding as %s instead", new_path); - update_file(0, dst_other.sha1, dst_other.mode, new_path); + update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; @@ -969,7 +966,7 @@ static int process_renames(struct merge_options *o, b = ren1->pair->two; a = &src_other; } - mfi = merge_file(one, a, b, + mfi = merge_file(o, one, a, b, o->branch1, o->branch2); if (mfi.clean && @@ -991,11 +988,11 @@ static int process_renames(struct merge_options *o, ren1_dst); clean_merge = 0; - if (!index_only) + if (!o->call_depth) update_stages(ren1_dst, one, a, b, 1); } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst); } } } @@ -1037,7 +1034,7 @@ static int process_entry(struct merge_options *o, if (a_sha) output(o, 2, "Removing %s", path); /* do not touch working file if it did not exist */ - remove_file(1, path, !a_sha); + remove_file(o, 1, path, !a_sha); } else { /* Deleted in one and changed in the other */ clean_merge = 0; @@ -1046,13 +1043,13 @@ static int process_entry(struct merge_options *o, "and modified in %s. Version %s of %s left in tree.", path, o->branch1, o->branch2, o->branch2, path); - update_file(0, b_sha, b_mode, path); + update_file(o, 0, b_sha, b_mode, path); } else { output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, o->branch2, o->branch1, o->branch1, path); - update_file(0, a_sha, a_mode, path); + update_file(o, 0, a_sha, a_mode, path); } } @@ -1084,11 +1081,11 @@ static int process_entry(struct merge_options *o, output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); - remove_file(0, path, 0); - update_file(0, sha, mode, new_path); + remove_file(o, 0, path, 0); + update_file(o, 0, sha, mode, new_path); } else { output(o, 2, "Adding %s", path); - update_file(1, sha, mode, path); + update_file(o, 1, sha, mode, path); } } else if (a_sha && b_sha) { /* Case C: Added in both (check for same permissions) and */ @@ -1110,12 +1107,12 @@ static int process_entry(struct merge_options *o, hashcpy(b.sha1, b_sha); b.mode = b_mode; - mfi = merge_file(&one, &a, &b, + mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2); clean_merge = mfi.clean; if (mfi.clean) - update_file(1, mfi.sha, mfi.mode, path); + update_file(o, 1, mfi.sha, mfi.mode, path); else if (S_ISGITLINK(mfi.mode)) output(o, 1, "CONFLICT (submodule): Merge conflict in %s " "- needs %s", path, sha1_to_hex(b.sha1)); @@ -1123,10 +1120,10 @@ static int process_entry(struct merge_options *o, output(o, 1, "CONFLICT (%s): Merge conflict in %s", reason, path); - if (index_only) - update_file(0, mfi.sha, mfi.mode, path); + if (o->call_depth) + update_file(o, 0, mfi.sha, mfi.mode, path); else - update_file_flags(mfi.sha, mfi.mode, path, + update_file_flags(o, mfi.sha, mfi.mode, path, 0 /* update_cache */, 1 /* update_working_directory */); } } else if (!o_sha && !a_sha && !b_sha) { @@ -1134,7 +1131,7 @@ static int process_entry(struct merge_options *o, * this entry was deleted altogether. a_mode == 0 means * we had that path and want to actively remove it. */ - remove_file(1, path, !a_mode); + remove_file(o, 1, path, !a_mode); } else die("Fatal merge failure, shouldn't happen."); @@ -1160,7 +1157,7 @@ int merge_trees(struct merge_options *o, return 1; } - code = git_merge_trees(index_only, common, head, merge); + code = git_merge_trees(o->call_depth, common, head, merge); if (code != 0) die("merging of trees %s and %s failed", @@ -1195,7 +1192,7 @@ int merge_trees(struct merge_options *o, else clean = 1; - if (index_only) + if (o->call_depth) *result = write_tree_from_memory(o); return clean; @@ -1281,16 +1278,13 @@ int merge_recursive(struct merge_options *o, } discard_cache(); - if (!o->call_depth) { + if (!o->call_depth) read_cache(); - index_only = 0; - } else - index_only = 1; clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, &mrtree); - if (index_only) { + if (o->call_depth) { *result = make_virtual_commit(mrtree, "merged tree"); commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); From c7d849243a4be2c60ff48cc42ea8b0518bb7f27f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 3 Sep 2008 02:30:03 +0200 Subject: [PATCH 08/11] merge-recursive: move the global obuf to struct merge_options Signed-off-by: Miklos Vajna --- merge-recursive.c | 37 ++++++++++++++++++------------------- merge-recursive.h | 1 + 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index c426589d11..d4f12d0a4d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -80,18 +80,16 @@ struct stage_data static struct string_list current_file_set = {NULL, 0, 0, 1}; static struct string_list current_directory_set = {NULL, 0, 0, 1}; -static struct strbuf obuf = STRBUF_INIT; - static int show(struct merge_options *o, int v) { return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5; } -static void flush_output(void) +static void flush_output(struct merge_options *o) { - if (obuf.len) { - fputs(obuf.buf, stdout); - strbuf_reset(&obuf); + if (o->obuf.len) { + fputs(o->obuf.buf, stdout); + strbuf_reset(&o->obuf); } } @@ -103,35 +101,35 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) if (!show(o, v)) return; - strbuf_grow(&obuf, o->call_depth * 2 + 2); - memset(obuf.buf + obuf.len, ' ', o->call_depth * 2); - strbuf_setlen(&obuf, obuf.len + o->call_depth * 2); + strbuf_grow(&o->obuf, o->call_depth * 2 + 2); + memset(o->obuf.buf + o->obuf.len, ' ', o->call_depth * 2); + strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2); va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap); va_end(ap); if (len < 0) len = 0; - if (len >= strbuf_avail(&obuf)) { - strbuf_grow(&obuf, len + 2); + if (len >= strbuf_avail(&o->obuf)) { + strbuf_grow(&o->obuf, len + 2); va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap); va_end(ap); - if (len >= strbuf_avail(&obuf)) { + if (len >= strbuf_avail(&o->obuf)) { die("this should not happen, your snprintf is broken"); } } - strbuf_setlen(&obuf, obuf.len + len); - strbuf_add(&obuf, "\n", 1); + strbuf_setlen(&o->obuf, o->obuf.len + len); + strbuf_add(&o->obuf, "\n", 1); if (!o->buffer_output) - flush_output(); + flush_output(o); } static void output_commit_title(struct merge_options *o, struct commit *commit) { int i; - flush_output(); + flush_output(o); for (i = o->call_depth; i--;) fputs(" ", stdout); if (commit->util) @@ -1289,7 +1287,7 @@ int merge_recursive(struct merge_options *o, commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); } - flush_output(); + flush_output(o); return clean; } @@ -1375,4 +1373,5 @@ void init_merge_options(struct merge_options *o) strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); if (o->verbosity >= 5) o->buffer_output = 0; + strbuf_init(&o->obuf, 0); } diff --git a/merge-recursive.h b/merge-recursive.h index 4f5537417e..be84d9bb32 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -10,6 +10,7 @@ struct merge_options { int diff_rename_limit; int merge_rename_limit; int call_depth; + struct strbuf obuf; }; /* merge_trees() but with recursive ancestor consolidation */ From 696ee23cc1817a5064780600374589d3a773a081 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 3 Sep 2008 19:08:56 +0200 Subject: [PATCH 09/11] merge-recursive: move current_{file,directory}_set to struct merge_options Signed-off-by: Miklos Vajna --- merge-recursive.c | 57 +++++++++++++++++++++++++---------------------- merge-recursive.h | 4 ++++ 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index d4f12d0a4d..1c24c31e5b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -77,9 +77,6 @@ struct stage_data unsigned processed:1; }; -static struct string_list current_file_set = {NULL, 0, 0, 1}; -static struct string_list current_directory_set = {NULL, 0, 0, 1}; - static int show(struct merge_options *o, int v) { return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5; @@ -232,25 +229,27 @@ static int save_files_dirs(const unsigned char *sha1, { int len = strlen(path); char *newpath = xmalloc(baselen + len + 1); + struct merge_options *o = context; + memcpy(newpath, base, baselen); memcpy(newpath + baselen, path, len); newpath[baselen + len] = '\0'; if (S_ISDIR(mode)) - string_list_insert(newpath, ¤t_directory_set); + string_list_insert(newpath, &o->current_directory_set); else - string_list_insert(newpath, ¤t_file_set); + string_list_insert(newpath, &o->current_file_set); free(newpath); return READ_TREE_RECURSIVE; } -static int get_files_dirs(struct tree *tree) +static int get_files_dirs(struct merge_options *o, struct tree *tree) { int n; - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL)) + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o)) return 0; - n = current_file_set.nr + current_directory_set.nr; + n = o->current_file_set.nr + o->current_directory_set.nr; return n; } @@ -434,7 +433,7 @@ static int remove_file(struct merge_options *o, int clean, return 0; } -static char *unique_path(const char *path, const char *branch) +static char *unique_path(struct merge_options *o, const char *path, const char *branch) { char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); int suffix = 0; @@ -446,12 +445,12 @@ static char *unique_path(const char *path, const char *branch) for (; *p; ++p) if ('/' == *p) *p = '_'; - while (string_list_has_string(¤t_file_set, newpath) || - string_list_has_string(¤t_directory_set, newpath) || + while (string_list_has_string(&o->current_file_set, newpath) || + string_list_has_string(&o->current_directory_set, newpath) || lstat(newpath, &st) == 0) sprintf(p, "_%d", suffix++); - string_list_insert(newpath, ¤t_file_set); + string_list_insert(newpath, &o->current_file_set); return newpath; } @@ -716,14 +715,14 @@ static void conflict_rename_rename(struct merge_options *o, const char *ren2_dst = ren2->pair->two->path; const char *dst_name1 = ren1_dst; const char *dst_name2 = ren2_dst; - if (string_list_has_string(¤t_directory_set, ren1_dst)) { - dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + if (string_list_has_string(&o->current_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1); output(o, 1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); remove_file(o, 0, ren1_dst, 0); } - if (string_list_has_string(¤t_directory_set, ren2_dst)) { - dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + if (string_list_has_string(&o->current_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2); output(o, 1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); remove_file(o, 0, ren2_dst, 0); @@ -749,7 +748,7 @@ static void conflict_rename_dir(struct merge_options *o, struct rename *ren1, const char *branch1) { - char *new_path = unique_path(ren1->pair->two->path, branch1); + char *new_path = unique_path(o, ren1->pair->two->path, branch1); output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(o, 0, ren1->pair->two->path, 0); update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); @@ -762,8 +761,8 @@ static void conflict_rename_rename_2(struct merge_options *o, struct rename *ren2, const char *branch2) { - char *new_path1 = unique_path(ren1->pair->two->path, branch1); - char *new_path2 = unique_path(ren2->pair->two->path, branch2); + char *new_path1 = unique_path(o, ren1->pair->two->path, branch1); + char *new_path2 = unique_path(o, ren2->pair->two->path, branch2); output(o, 1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); @@ -913,7 +912,7 @@ static int process_renames(struct merge_options *o, try_merge = 0; - if (string_list_has_string(¤t_directory_set, ren1_dst)) { + if (string_list_has_string(&o->current_directory_set, ren1_dst)) { clean_merge = 0; output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", @@ -935,7 +934,7 @@ static int process_renames(struct merge_options *o, "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); - new_path = unique_path(ren1_dst, branch2); + new_path = unique_path(o, ren1_dst, branch2); output(o, 1, "Adding as %s instead", new_path); update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { @@ -1073,8 +1072,8 @@ static int process_entry(struct merge_options *o, sha = b_sha; conf = "directory/file"; } - if (string_list_has_string(¤t_directory_set, path)) { - const char *new_path = unique_path(path, add_branch); + if (string_list_has_string(&o->current_directory_set, path)) { + const char *new_path = unique_path(o, path, add_branch); clean_merge = 0; output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", @@ -1165,10 +1164,10 @@ int merge_trees(struct merge_options *o, if (unmerged_cache()) { struct string_list *entries, *re_head, *re_merge; int i; - string_list_clear(¤t_file_set, 1); - string_list_clear(¤t_directory_set, 1); - get_files_dirs(head); - get_files_dirs(merge); + string_list_clear(&o->current_file_set, 1); + string_list_clear(&o->current_directory_set, 1); + get_files_dirs(o, head); + get_files_dirs(o, merge); entries = get_unmerged(); re_head = get_renames(o, head, common, head, merge, entries); @@ -1374,4 +1373,8 @@ void init_merge_options(struct merge_options *o) if (o->verbosity >= 5) o->buffer_output = 0; strbuf_init(&o->obuf, 0); + memset(&o->current_file_set, 0, sizeof(struct string_list)); + o->current_file_set.strdup_strings = 1; + memset(&o->current_directory_set, 0, sizeof(struct string_list)); + o->current_directory_set.strdup_strings = 1; } diff --git a/merge-recursive.h b/merge-recursive.h index be84d9bb32..fd138ca140 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -1,6 +1,8 @@ #ifndef MERGE_RECURSIVE_H #define MERGE_RECURSIVE_H +#include "string-list.h" + struct merge_options { const char *branch1; const char *branch2; @@ -11,6 +13,8 @@ struct merge_options { int merge_rename_limit; int call_depth; struct strbuf obuf; + struct string_list current_file_set; + struct string_list current_directory_set; }; /* merge_trees() but with recursive ancestor consolidation */ From a6f63ae002237c2eb416d4e2cb43227522e4ea9a Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 5 Sep 2008 19:26:42 +0200 Subject: [PATCH 10/11] merge-recursive: get rid of virtual_id We now just leave the object->sha1 field of virtual commits 0{40} as it is initialized, as a unique hash is not necessary in case of virtual commits. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- merge-recursive.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 1c24c31e5b..dbdb9ac2c4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -35,18 +35,14 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two) } /* - * A virtual commit has - * - (const char *)commit->util set to the name, and - * - *(int *)commit->object.sha1 set to the virtual id. + * A virtual commit has (const char *)commit->util set to the name. */ struct commit *make_virtual_commit(struct tree *tree, const char *comment) { struct commit *commit = xcalloc(1, sizeof(struct commit)); - static unsigned virtual_id = 1; commit->tree = tree; commit->util = (void*)comment; - *(int*)commit->object.sha1 = virtual_id++; /* avoid warnings */ commit->object.parsed = 1; return commit; From 4271666046b4b3e655eab88821096539099f3b14 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 6 Sep 2008 18:29:49 +0200 Subject: [PATCH 11/11] builtin-merge: release the lockfile in try_merge_strategy() Once we committed the locked index, we should release the lockfile. In most cases this is done automatically when the process ends, but this is not true in this case. [jc: with additional tests from Eric Raible] Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-merge.c | 1 + t/t6026-merge-attr.sh | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/builtin-merge.c b/builtin-merge.c index b857cf6246..bb09e6fb34 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -576,6 +576,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) die ("unable to write %s", get_index_file()); + rollback_lock_file(lock); return clean ? 0 : 1; } else { args = xmalloc((4 + commit_list_count(common) + diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index 56fc341768..86f47ca595 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -142,4 +142,26 @@ test_expect_success 'custom merge backend' ' rm -f $o $a $b ' +test_expect_success 'up-to-date merge without common ancestor' ' + test_create_repo repo1 && + test_create_repo repo2 && + test_tick && + ( + cd repo1 && + >a && + git add a && + git commit -m initial + ) && + test_tick && + ( + cd repo2 && + git commit --allow-empty -m initial + ) && + test_tick && + ( + cd repo1 && + git pull ../repo2 master + ) +' + test_done