X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=merge-recursive.c;h=fa320eb6b1a9e257167e7f843fe36cbdfd908726;hb=717d1462ba0ebc430775eccaae65eb9b8234eb32;hp=611cd95cf597b0ccf06cf9b4318eb34a86a4c05a;hpb=284fe4beb6b4835ef6857cd56b34c70c1d12b14d;p=git.git diff --git a/merge-recursive.c b/merge-recursive.c index 611cd95cf..fa320eb6b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3,13 +3,6 @@ * Fredrik Kuivinen. * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 */ -#include -#include -#include -#include -#include -#include -#include #include "cache.h" #include "cache-tree.h" #include "commit.h" @@ -21,6 +14,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -73,32 +67,80 @@ struct stage_data unsigned processed:1; }; +struct output_buffer +{ + struct output_buffer *next; + char *str; +}; + static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; -static int output_indent = 0; +static int call_depth = 0; +static int verbosity = 2; +static int buffer_output = 1; +static int do_progress = 1; +static unsigned last_percent; +static unsigned merged_cnt; +static unsigned total_cnt; +static volatile sig_atomic_t progress_update; +static struct output_buffer *output_list, *output_end; + +static int show (int v) +{ + return (!call_depth && verbosity >= v) || verbosity >= 5; +} -static void output(const char *fmt, ...) +static void output(int v, const char *fmt, ...) { va_list args; - int i; - for (i = output_indent; i--;) - fputs(" ", stdout); va_start(args, fmt); - vfprintf(stdout, fmt, args); + if (buffer_output && show(v)) { + struct output_buffer *b = xmalloc(sizeof(*b)); + nfvasprintf(&b->str, fmt, args); + b->next = NULL; + if (output_end) + output_end->next = b; + else + output_list = b; + output_end = b; + } else if (show(v)) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + vfprintf(stdout, fmt, args); + fputc('\n', stdout); + } va_end(args); - fputc('\n', stdout); +} + +static void flush_output() +{ + struct output_buffer *b, *n; + for (b = output_list; b; b = n) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + fputs(b->str, stdout); + fputc('\n', stdout); + n = b->next; + free(b->str); + free(b); + } + output_list = NULL; + output_end = NULL; } static void output_commit_title(struct commit *commit) { int i; - for (i = output_indent; i--;) + flush_output(); + for (i = call_depth; i--;) fputs(" ", stdout); if (commit->util) printf("virtual %s\n", (char *)commit->util); else { - printf("%s ", sha1_to_hex(commit->object.sha1)); + printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); if (parse_commit(commit) != 0) printf("(bad commit)\n"); else { @@ -116,33 +158,37 @@ static void output_commit_title(struct commit *commit) } } -static const char *current_index_file = NULL; -static const char *original_index_file; -static const char *temporary_index_file; -static int cache_dirty = 0; +static void progress_interval(int signum) +{ + progress_update = 1; +} -static int flush_cache(void) +static void setup_progress_signal(void) { - /* flush temporary index */ - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int fd = hold_lock_file_for_update(lock, current_index_file, 1); - if (write_cache(fd, active_cache, active_nr) || - close(fd) || commit_lock_file(lock)) - die ("unable to write %s", current_index_file); - discard_cache(); - cache_dirty = 0; - return 0; + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); } -static void setup_index(int temp) +static void display_progress() { - current_index_file = temp ? temporary_index_file: original_index_file; - if (cache_dirty) { - discard_cache(); - cache_dirty = 0; + unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0; + if (progress_update || percent != last_percent) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, merged_cnt, total_cnt); + progress_update = 0; + last_percent = percent; } - unlink(temporary_index_file); - discard_cache(); } static struct cache_entry *make_cache_entry(unsigned int mode, @@ -173,9 +219,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh, int options) { struct cache_entry *ce; - if (!cache_dirty) - read_cache_from(current_index_file); - cache_dirty++; ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); if (!ce) return error("cache_addinfo failed: %s", strerror(cache_errno)); @@ -193,26 +236,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, */ static int index_only = 0; -static int git_read_tree(struct tree *tree) -{ - int rc; - struct object_list *trees = NULL; - struct unpack_trees_options opts; - - if (cache_dirty) - die("read-tree with dirty cache"); - - memset(&opts, 0, sizeof(opts)); - object_list_append(&tree->object, &trees); - rc = unpack_trees(trees, &opts); - cache_tree_free(&active_cache_tree); - - if (rc == 0) - cache_dirty = 1; - - return rc; -} - static int git_merge_trees(int index_only, struct tree *common, struct tree *head, @@ -222,11 +245,6 @@ static int git_merge_trees(int index_only, struct object_list *trees = NULL; struct unpack_trees_options opts; - if (!cache_dirty) { - read_cache_from(current_index_file); - cache_dirty = 1; - } - memset(&opts, 0, sizeof(opts)); if (index_only) opts.index_only = 1; @@ -242,39 +260,37 @@ static int git_merge_trees(int index_only, rc = unpack_trees(trees, &opts); cache_tree_free(&active_cache_tree); - - cache_dirty = 1; - return rc; } +static int unmerged_index(void) +{ + int i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return 1; + } + return 0; +} + static struct tree *git_write_tree(void) { struct tree *result = NULL; - if (cache_dirty) { - unsigned i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) - return NULL; - } - } else - read_cache_from(current_index_file); + if (unmerged_index()) + 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) + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) die("error building trees"); result = lookup_tree(active_cache_tree->sha1); - flush_cache(); - cache_dirty = 0; - return result; } @@ -337,14 +353,14 @@ static struct path_list *get_unmerged(void) int i; unmerged->strdup_paths = 1; - if (!cache_dirty) { - read_cache_from(current_index_file); - cache_dirty++; - } - for (i = 0; i < active_nr; i++) { + total_cnt += active_nr; + + for (i = 0; i < active_nr; i++, merged_cnt++) { struct path_list_item *item; struct stage_data *e; struct cache_entry *ce = active_cache[i]; + if (do_progress) + display_progress(); if (!ce_stage(ce)) continue; @@ -427,8 +443,9 @@ static struct path_list *get_renames(struct tree *tree, return renames; } -int update_stages(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, int clear) +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) @@ -468,20 +485,16 @@ static int remove_path(const char *name) return ret; } -int remove_file(int clean, const char *path) +static int remove_file(int clean, const char *path, int no_wd) { int update_cache = index_only || clean; - int update_working_directory = !index_only; + int update_working_directory = !index_only && !no_wd; if (update_cache) { - if (!cache_dirty) - read_cache_from(current_index_file); - cache_dirty++; if (remove_file_from_cache(path)) return -1; } - if (update_working_directory) - { + if (update_working_directory) { unlink(path); if (errno != ENOENT || errno != EISDIR) return -1; @@ -523,7 +536,7 @@ static int mkdir_p(const char *path, unsigned long mode) static void flush_buffer(int fd, const char *buf, unsigned long size) { while (size > 0) { - long ret = xwrite(fd, buf, size); + long ret = write_in_full(fd, buf, size); if (ret < 0) { /* Ignore epipe */ if (errno == EPIPE) @@ -537,11 +550,11 @@ static void flush_buffer(int fd, const char *buf, unsigned long size) } } -void update_file_flags(const unsigned char *sha, - unsigned mode, - const char *path, - int update_cache, - int update_wd) +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; @@ -586,10 +599,10 @@ void update_file_flags(const unsigned char *sha, add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } -void update_file(int clean, - const unsigned char *sha, - unsigned mode, - const char *path) +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); } @@ -604,24 +617,21 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) - die("unable to read blob object %s", sha1_to_hex(sha1)); + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +662,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + mmfile_t orig, src1, src2; + mmbuffer_t result_buf; + xpparam_t xpp; + 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); + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(&orig, + &src1, name1, + &src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + &result_buf); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + + 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_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); @@ -722,15 +724,15 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (path_list_has_path(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output("%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s added as %s instead", ren1_dst, branch2, dst_name1); - remove_file(0, ren1_dst); + remove_file(0, ren1_dst, 0); } if (path_list_has_path(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output("%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s added as %s instead", ren2_dst, branch1, dst_name2); - remove_file(0, ren2_dst); + remove_file(0, ren2_dst, 0); } update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); @@ -742,8 +744,8 @@ static void conflict_rename_dir(struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output("Renaming %s to %s instead", ren1->pair->one->path, new_path); - remove_file(0, ren1->pair->two->path); + output(1, "Renamed %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); } @@ -755,10 +757,10 @@ static void conflict_rename_rename_2(struct rename *ren1, { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output("Renaming %s to %s and %s to %s instead", + output(1, "Renamed %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); + 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); @@ -848,7 +850,7 @@ static int process_renames(struct path_list *a_renames, ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { clean_merge = 0; - output("CONFLICT (rename/rename): " + output(1, "CONFLICT (rename/rename): " "Rename %s->%s in branch %s " "rename %s->%s in %s", src, ren1_dst, branch1, @@ -856,20 +858,20 @@ static int process_renames(struct path_list *a_renames, conflict_rename_rename(ren1, branch1, ren2, branch2); } else { struct merge_file_info mfi; - remove_file(1, ren1_src); + 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("Renaming %s->%s", src, ren1_dst); + output(1, "Renamed %s->%s", src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (content): merge conflict in %s", + output(1, "CONFLICT (content): merge conflict in %s", ren1_dst); clean_merge = 0; @@ -889,7 +891,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src); + remove_file(1, ren1_src, index_only); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -900,14 +902,14 @@ static int process_renames(struct path_list *a_renames, if (path_list_has_path(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output("CONFLICT (rename/directory): Rename %s->%s in %s " + output(1, "CONFLICT (rename/directory): Renamed %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("CONFLICT (rename/delete): Rename %s->%s in %s " + output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -916,19 +918,19 @@ static int process_renames(struct path_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output("CONFLICT (rename/add): Rename %s->%s in %s. " + output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output("Adding as %s instead", new_path); + output(1, "Added as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output("CONFLICT (rename/rename): Rename %s->%s in %s. " - "Rename %s->%s in %s", + output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. " + "Renamed %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); @@ -952,11 +954,11 @@ static int process_renames(struct path_list *a_renames, a_branch, b_branch); if (mfi.merge || !mfi.clean) - output("Renaming %s => %s", ren1_src, ren1_dst); + output(1, "Renamed %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (rename/modify): Merge conflict in %s", + output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); clean_merge = 0; @@ -971,8 +973,6 @@ static int process_renames(struct path_list *a_renames, path_list_clear(&a_by_dst, 0); path_list_clear(&b_by_dst, 0); - if (cache_dirty) - flush_cache(); return clean_merge; } @@ -1006,19 +1006,20 @@ 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("Removing %s", path); - remove_file(1, path); + output(2, "Removed %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("CONFLICT (delete/modify): %s deleted in %s " + 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("CONFLICT (delete/modify): %s deleted in %s " + 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); @@ -1051,48 +1052,27 @@ static int process_entry(const char *path, struct stage_data *entry, if (path_list_has_path(¤t_directory_set, path)) { const char *new_path = unique_path(path, add_branch); clean_merge = 0; - output("CONFLICT (%s): There is a directory with name %s in %s. " - "Adding %s as %s", + output(1, "CONFLICT (%s): There is a directory with name %s in %s. " + "Added %s as %s", conf, path, other_branch, path, new_path); - remove_file(0, path); + remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output("Adding %s", path); + output(2, "Added %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } 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; - output("Auto-merging %s", path); + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } + output(2, "Auto-merged %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); o.mode = o_mode; @@ -1108,7 +1088,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output(1, "CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); @@ -1119,9 +1100,6 @@ static int process_entry(const char *path, struct stage_data *entry, } else die("Fatal merge failure, shouldn't happen."); - if (cache_dirty) - flush_cache(); - return clean_merge; } @@ -1134,7 +1112,7 @@ static int merge_trees(struct tree *head, { int code, clean; if (sha_eq(common->object.sha1, merge->object.sha1)) { - output("Already uptodate!"); + output(0, "Already uptodate!"); *result = head; return 1; } @@ -1146,9 +1124,7 @@ static int merge_trees(struct tree *head, sha1_to_hex(head->object.sha1), sha1_to_hex(merge->object.sha1)); - *result = git_write_tree(); - - if (!*result) { + if (unmerged_index()) { struct path_list *entries, *re_head, *re_merge; int i; path_list_clear(¤t_file_set, 1); @@ -1161,30 +1137,27 @@ static int merge_trees(struct tree *head, 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++) { + total_cnt += entries->nr; + for (i = 0; i < entries->nr; i++, merged_cnt++) { const char *path = entries->items[i].path; struct stage_data *e = entries->items[i].util; - if (e->processed) - continue; - if (!process_entry(path, e, branch1, branch2)) + if (!e->processed + && !process_entry(path, e, branch1, branch2)) clean = 0; + if (do_progress) + display_progress(); } path_list_clear(re_merge, 0); path_list_clear(re_head, 0); path_list_clear(entries, 1); - if (clean || index_only) - *result = git_write_tree(); - else - *result = NULL; - } else { - clean = 1; - printf("merging of trees %s and %s resulted in %s\n", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1), - sha1_to_hex((*result)->object.sha1)); } + else + clean = 1; + + if (index_only) + *result = git_write_tree(); return clean; } @@ -1204,49 +1177,48 @@ 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 cleaness of the merge. */ -static -int merge(struct commit *h1, - struct commit *h2, - const char *branch1, - const char *branch2, - int call_depth /* =0 */, - struct commit *ancestor /* =None */, - struct commit **result) +static int merge(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + struct commit_list *ca, + struct commit **result) { - struct commit_list *ca = NULL, *iter; + struct commit_list *iter; struct commit *merged_common_ancestors; struct tree *mrtree; int clean; - output("Merging:"); - output_commit_title(h1); - output_commit_title(h2); + if (show(4)) { + output(4, "Merging:"); + output_commit_title(h1); + output_commit_title(h2); + } - if (ancestor) - commit_list_insert(ancestor, &ca); - else - ca = reverse_commit_list(get_merge_bases(h1, h2, 1)); + if (!ca) { + ca = get_merge_bases(h1, h2, 1); + ca = reverse_commit_list(ca); + } - output("found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); + 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)); - unsigned char hdr[40]; - int hdrlen; tree->object.parsed = 1; tree->object.type = OBJ_TREE; - write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1, - hdr, &hdrlen); + write_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } for (iter = ca; iter; iter = iter->next) { - output_indent = call_depth + 1; + call_depth++; /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is @@ -1254,40 +1226,58 @@ int merge(struct commit *h1, * merge_trees has always overwritten it: the commited * "conflicts" were already resolved. */ + discard_cache(); merge(merged_common_ancestors, iter->item, "Temporary merge branch 1", "Temporary merge branch 2", - call_depth + 1, NULL, &merged_common_ancestors); - output_indent = call_depth; + call_depth--; if (!merged_common_ancestors) die("merge returned no commit"); } - if (call_depth == 0) { - setup_index(0 /* $GIT_DIR/index */); + discard_cache(); + if (!call_depth) { + read_cache(); index_only = 0; - } else { - setup_index(1 /* temporary index */); - git_read_tree(h1->tree); + } else index_only = 1; - } clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, branch1, branch2, &mrtree); - if (!ancestor && (clean || index_only)) { + if (index_only) { *result = make_virtual_commit(mrtree, "merged tree"); commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); - } else - *result = NULL; - + } + if (!call_depth && do_progress) { + /* Make sure we end at 100% */ + if (!total_cnt) + total_cnt = 1; + merged_cnt = total_cnt; + progress_update = 1; + display_progress(); + fputc('\n', stderr); + } + flush_output(); return clean; } +static const char *better_branch_name(const char *branch) +{ + static char githead_env[8 + 40 + 1]; + char *name; + + if (strlen(branch) != 40) + return branch; + sprintf(githead_env, "GITHEAD_%s", branch); + name = getenv(githead_env); + return name ? name : branch; +} + static struct commit *get_ref(const char *ref) { unsigned char sha1[20]; @@ -1296,6 +1286,9 @@ static struct commit *get_ref(const char *ref) if (get_sha1(ref, sha1)) die("Could not resolve ref '%s'", ref); object = deref_tag(parse_object(sha1), ref, strlen(ref)); + 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)) @@ -1303,20 +1296,29 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } +static int merge_config(const char *var, const char *value) +{ + if (!strcasecmp(var, "merge.verbosity")) { + verbosity = git_config_int(var, value); + return 0; + } + return git_default_config(var, value); +} + int main(int argc, char *argv[]) { - static const char *bases[2]; + static const char *bases[20]; static unsigned bases_count = 0; int i, clean; 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; - original_index_file = getenv("GIT_INDEX_FILE"); - - if (!original_index_file) - original_index_file = xstrdup(git_path("index")); - - temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx")); + git_config(merge_config); + if (getenv("GIT_MERGE_VERBOSITY")) + verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); @@ -1329,26 +1331,39 @@ int main(int argc, char *argv[]) } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); + if (verbosity >= 5) { + buffer_output = 0; + do_progress = 0; + } + else + do_progress = isatty(1); branch1 = argv[++i]; branch2 = argv[++i]; - printf("Merging %s with %s\n", branch1, branch2); h1 = get_ref(branch1); h2 = get_ref(branch2); - if (bases_count == 1) { - struct commit *ancestor = get_ref(bases[0]); - clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); - } else - clean = merge(h1, h2, branch1, branch2, 0, NULL, &result); + branch1 = better_branch_name(branch1); + branch2 = better_branch_name(branch2); + + if (do_progress) + setup_progress_signal(); + if (show(3)) + printf("Merging %s with %s\n", branch1, branch2); - if (cache_dirty) - flush_cache(); + index_fd = hold_lock_file_for_update(lock, get_index_file(), 1); + + for (i = 0; i < bases_count; i++) { + struct commit *ancestor = get_ref(bases[i]); + ca = commit_list_insert(ancestor, &ca); + } + clean = merge(h1, h2, branch1, branch2, ca, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + close(index_fd) || commit_lock_file(lock))) + die ("unable to write %s", get_index_file()); return clean ? 0: 1; } - -/* -vim: sw=8 noet -*/