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)
va_end(args);
}
-static void flush_output()
+static void flush_output(void)
{
struct output_buffer *b, *n;
for (b = output_list; b; b = n) {
}
}
-static void progress_interval(int signum)
-{
- progress_update = 1;
-}
-
-static void setup_progress_signal(void)
-{
- 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 display_progress()
-{
- 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;
- }
-}
-
static struct cache_entry *make_cache_entry(unsigned int mode,
const unsigned char *sha1, const char *path, int stage, int refresh)
{
int i;
unmerged->strdup_paths = 1;
- total_cnt += active_nr;
- for (i = 0; i < active_nr; i++, merged_cnt++) {
+ for (i = 0; i < active_nr; i++) {
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;
}
}
+static int make_room_for_path(const char *path)
+{
+ int status;
+ const char *msg = "failed to create path '%s'%s";
+
+ status = mkdir_p(path, 0777);
+ 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,
if (type != OBJ_BLOB)
die("blob expected for %s '%s'", sha1_to_hex(sha), path);
+ if (make_room_for_path(path) < 0) {
+ update_wd = 0;
+ goto update_index;
+ }
if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
int fd;
- if (mkdir_p(path, 0777))
- die("failed to create path %s: %s", path, strerror(errno));
- unlink(path);
if (mode & 0100)
mode = 0777;
else
die("do not know what to do with %06o %s '%s'",
mode, sha1_to_hex(sha), path);
}
+ update_index:
if (update_cache)
add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
}
mm->size = size;
}
-/* Low-level merge functions */
-typedef int (*ll_merge_fn)(const char *cmd,
+/*
+ * Customizable low-level merge drivers support.
+ */
+
+struct ll_merge_driver;
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+ const char *path,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
mmbuffer_t *result);
-static int ll_xdl_merge(const char *cmd__unused,
+struct ll_merge_driver {
+ const char *name;
+ const char *description;
+ ll_merge_fn fn;
+ const char *recursive;
+ struct ll_merge_driver *next;
+ char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+ const char *path_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
{
xpparam_t xpp;
+ if (buffer_is_binary(orig->ptr, orig->size) ||
+ buffer_is_binary(src1->ptr, src1->size) ||
+ buffer_is_binary(src2->ptr, src2->size))
+ return error("Cannot merge binary files: %s vs. %s\n",
+ name1, name2);
+
memset(&xpp, 0, sizeof(xpp));
return xdl_merge(orig,
src1, name1,
result);
}
-static int ll_union_merge(const char *cmd__unused,
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+ const char *path_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
long size;
const int marker_size = 7;
- int status = ll_xdl_merge(cmd__unused, orig,
- src1, NULL, src2, NULL, result);
+ int status = ll_xdl_merge(drv_unused, path_unused,
+ orig, src1, NULL, src2, NULL, result);
if (status <= 0)
return status;
size = result->size;
return 0;
}
-static int ll_binary_merge(const char *cmd__unused,
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+ const char *path_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
return 1;
}
-static struct {
- const char *name;
- ll_merge_fn fn;
-} ll_merge_fns[] = {
- { "binary", ll_binary_merge },
- { "text", ll_xdl_merge },
- { "union", ll_union_merge },
- { NULL, NULL },
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+ { "binary", "built-in binary merge", ll_binary_merge },
+ { "text", "built-in 3-way text merge", ll_xdl_merge },
+ { "union", "built-in union merge", ll_union_merge },
};
static void create_temp(mmfile_t *src, char *path)
close(fd);
}
-static int ll_ext_merge(const char *cmd,
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+ const char *path,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int status, fd, i;
struct stat st;
+ if (fn->cmdline == NULL)
+ die("custom merge driver %s lacks command line.", fn->name);
+
result->ptr = NULL;
result->size = 0;
create_temp(orig, temp[0]);
interp_set_entry(table, 1, temp[1]);
interp_set_entry(table, 2, temp[2]);
- interpolate(cmdbuf, sizeof(cmdbuf), cmd, table, 3);
+ output(1, "merging %s using %s", path,
+ fn->description ? fn->description : fn->name);
+
+ interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
memset(&child, 0, sizeof(child));
child.argv = args;
/*
* merge.default and merge.driver configuration items
*/
-static struct user_merge_fn {
- struct user_merge_fn *next;
- const char *name;
- char *cmdline;
- char b_[1];
-} *ll_user_merge_fns, **ll_user_merge_fns_tail;
+static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
static const char *default_ll_merge;
static int read_merge_config(const char *var, const char *value)
{
- struct user_merge_fn *fn;
- int blen, nlen;
+ struct ll_merge_driver *fn;
+ const char *ep, *name;
+ int namelen;
if (!strcmp(var, "merge.default")) {
- default_ll_merge = strdup(value);
+ if (value)
+ default_ll_merge = strdup(value);
return 0;
}
- if (strcmp(var, "merge.driver"))
+ /*
+ * We are not interested in anything but "merge.<name>.variable";
+ * especially, we do not want to look at variables such as
+ * "merge.summary", "merge.tool", and "merge.verbosity".
+ */
+ if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
return 0;
- if (!value)
- return error("%s: lacks value", var);
+
/*
- * merge.driver is a multi-valued configuration, whose value is
- * of form:
- *
- * name command-line
- *
- * The command-line will be interpolated with the following
- * tokens and is given to the shell:
- *
- * %O - temporary file name for the merge base.
- * %A - temporary file name for our version.
- * %B - temporary file name for the other branches' version.
- *
- * The external merge driver should write the results in the file
- * named by %A, and signal that it has done with exit status 0.
+ * Find existing one as we might be processing merge.<name>.var2
+ * after seeing merge.<name>.var1.
*/
- for (nlen = -1, blen = 0; value[blen]; blen++)
- if (nlen < 0 && isspace(value[blen]))
- nlen = blen;
- if (nlen < 0)
- return error("%s '%s': lacks command line", var, value);
- fn = xcalloc(1, sizeof(struct user_merge_fn) + blen + 1);
- memcpy(fn->b_, value, blen + 1);
- fn->name = fn->b_;
- fn->b_[nlen] = 0;
- fn->cmdline = fn->b_ + nlen + 1;
- fn->next = *ll_user_merge_fns_tail;
- *ll_user_merge_fns_tail = fn;
+ name = var + 6;
+ namelen = ep - name;
+ for (fn = ll_user_merge; fn; fn = fn->next)
+ if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+ break;
+ if (!fn) {
+ char *namebuf;
+ fn = xcalloc(1, sizeof(struct ll_merge_driver));
+ namebuf = xmalloc(namelen + 1);
+ memcpy(namebuf, name, namelen);
+ namebuf[namelen] = 0;
+ fn->name = namebuf;
+ fn->fn = ll_ext_merge;
+ fn->next = NULL;
+ *ll_user_merge_tail = fn;
+ ll_user_merge_tail = &(fn->next);
+ }
+
+ ep++;
+
+ if (!strcmp("name", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ fn->description = strdup(value);
+ return 0;
+ }
+
+ if (!strcmp("driver", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ /*
+ * merge.<name>.driver specifies the command line:
+ *
+ * command-line
+ *
+ * The command-line will be interpolated with the following
+ * tokens and is given to the shell:
+ *
+ * %O - temporary file name for the merge base.
+ * %A - temporary file name for our version.
+ * %B - temporary file name for the other branches' version.
+ *
+ * The external merge driver should write the results in the
+ * file named by %A, and signal that it has done with zero exit
+ * status.
+ */
+ fn->cmdline = strdup(value);
+ return 0;
+ }
+
+ if (!strcmp("recursive", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ fn->recursive = strdup(value);
+ return 0;
+ }
+
return 0;
}
static void initialize_ll_merge(void)
{
- if (ll_user_merge_fns_tail)
+ if (ll_user_merge_tail)
return;
- ll_user_merge_fns_tail = &ll_user_merge_fns;
+ ll_user_merge_tail = &ll_user_merge;
git_config(read_merge_config);
}
-static ll_merge_fn find_ll_merge_fn(void *merge_attr, const char **cmdline)
+static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
{
- struct user_merge_fn *fn;
+ struct ll_merge_driver *fn;
const char *name;
int i;
initialize_ll_merge();
if (ATTR_TRUE(merge_attr))
- return ll_xdl_merge;
+ return &ll_merge_drv[LL_TEXT_MERGE];
else if (ATTR_FALSE(merge_attr))
- return ll_binary_merge;
+ return &ll_merge_drv[LL_BINARY_MERGE];
else if (ATTR_UNSET(merge_attr)) {
if (!default_ll_merge)
- return ll_xdl_merge;
+ return &ll_merge_drv[LL_TEXT_MERGE];
else
name = default_ll_merge;
}
else
name = merge_attr;
- for (fn = ll_user_merge_fns; fn; fn = fn->next) {
- if (!strcmp(fn->name, name)) {
- *cmdline = fn->cmdline;
- return ll_ext_merge;
- }
- }
+ for (fn = ll_user_merge; fn; fn = fn->next)
+ if (!strcmp(fn->name, name))
+ return fn;
- for (i = 0; ll_merge_fns[i].name; i++)
- if (!strcmp(ll_merge_fns[i].name, name))
- return ll_merge_fns[i].fn;
+ for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+ if (!strcmp(ll_merge_drv[i].name, name))
+ return &ll_merge_drv[i];
/* default to the 3-way */
- return ll_xdl_merge;
+ return &ll_merge_drv[LL_TEXT_MERGE];
}
-static void *git_path_check_merge(const char *path)
+static const char *git_path_check_merge(const char *path)
{
static struct git_attr_check attr_merge_check;
attr_merge_check.attr = git_attr("merge", 5);
if (git_checkattr(path, 1, &attr_merge_check))
- return ATTR__UNSET;
+ return NULL;
return attr_merge_check.value;
}
mmfile_t orig, src1, src2;
char *name1, *name2;
int merge_status;
- void *merge_attr;
- ll_merge_fn fn;
- const char *driver = NULL;
+ const char *ll_driver_name;
+ const struct ll_merge_driver *driver;
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
fill_mm(a->sha1, &src1);
fill_mm(b->sha1, &src2);
- merge_attr = git_path_check_merge(a->path);
- fn = find_ll_merge_fn(merge_attr, &driver);
+ ll_driver_name = git_path_check_merge(a->path);
+ driver = find_ll_merge_driver(ll_driver_name);
- merge_status = fn(driver, &orig,
- &src1, name1, &src2, name2, result_buf);
+ if (index_only && driver->recursive)
+ driver = find_ll_merge_driver(driver->recursive);
+ merge_status = driver->fn(driver, a->path,
+ &orig, &src1, name1, &src2, name2,
+ result_buf);
free(name1);
free(name2);
mfi = merge_file(o, a, b,
a_branch, b_branch);
- if (mfi.merge || !mfi.clean)
- output(1, "Renamed %s => %s", ren1_src, ren1_dst);
- if (mfi.merge)
- output(2, "Auto-merged %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);
+ 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, "Renamed %s => %s", ren1_src, ren1_dst);
+ if (mfi.merge)
+ output(2, "Auto-merged %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);
}
- update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
}
}
}
return clean_merge;
}
-static unsigned char *has_sha(const unsigned char *sha)
+static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
{
- return is_null_sha1(sha) ? NULL: (unsigned char *)sha;
+ return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
}
/* Per entry merge function */
print_index_entry("\tpath: ", entry);
*/
int clean_merge = 1;
- unsigned char *o_sha = has_sha(entry->stages[1].sha);
- unsigned char *a_sha = has_sha(entry->stages[2].sha);
- unsigned char *b_sha = has_sha(entry->stages[3].sha);
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 */
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.");
re_merge = get_renames(merge, common, head, merge, entries);
clean = process_renames(re_head, re_merge,
branch1, branch2);
- total_cnt += entries->nr;
- for (i = 0; i < entries->nr; i++, merged_cnt++) {
+ for (i = 0; i < entries->nr; i++) {
const char *path = entries->items[i].path;
struct stage_data *e = entries->items[i].util;
if (!e->processed
&& !process_entry(path, e, branch1, branch2))
clean = 0;
- if (do_progress)
- display_progress();
}
path_list_clear(re_merge, 0);
commit_list_insert(h1, &(*result)->parents);
commit_list_insert(h2, &(*result)->parents->next);
}
- 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;
}
}
if (argc - i != 3) /* "--" "<head>" "<remote>" */
die("Not handling anything other than two heads merge.");
- if (verbosity >= 5) {
+ if (verbosity >= 5)
buffer_output = 0;
- do_progress = 0;
- }
- else
- do_progress = isatty(1);
branch1 = argv[++i];
branch2 = argv[++i];
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);