Merge branch 'mv/merge-recursive'

* mv/merge-recursive:
  builtin-merge: release the lockfile in try_merge_strategy()
  merge-recursive: get rid of virtual_id
  merge-recursive: move current_{file,directory}_set to struct merge_options
  merge-recursive: move the global obuf to struct merge_options
  merge-recursive: get rid of the index_only global variable
  merge-recursive: move call_depth to struct merge_options
  cherry-pick/revert: make direct internal call to merge_tree()
  builtin-merge: avoid run_command_v_opt() for recursive and subtree
  merge-recursive: introduce merge_options
  merge-recursive.c: Add more generic merge_recursive_generic()
  Split out merge_recursive() to merge-recursive.c
This commit is contained in:
Shawn O. Pearce 2008-09-25 09:49:19 -07:00
commit ed520a8f27
8 changed files with 1580 additions and 1454 deletions

View file

@ -443,6 +443,7 @@ LIB_OBJS += log-tree.o
LIB_OBJS += mailmap.o LIB_OBJS += mailmap.o
LIB_OBJS += match-trees.o LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o LIB_OBJS += merge-file.o
LIB_OBJS += merge-recursive.o
LIB_OBJS += name-hash.o LIB_OBJS += name-hash.o
LIB_OBJS += object.o LIB_OBJS += object.o
LIB_OBJS += pack-check.o LIB_OBJS += pack-check.o

View file

@ -293,6 +293,7 @@ static int merge_working_tree(struct checkout_opts *opts,
*/ */
struct tree *result; struct tree *result;
struct tree *work; struct tree *work;
struct merge_options o;
if (!opts->merge) if (!opts->merge)
return 1; return 1;
parse_commit(old->commit); parse_commit(old->commit);
@ -311,13 +312,17 @@ static int merge_working_tree(struct checkout_opts *opts,
*/ */
add_files_to_cache(NULL, NULL, 0); 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); ret = reset_tree(new->commit->tree, opts, 1);
if (ret) if (ret)
return ret; return ret;
merge_trees(new->commit->tree, work, old->commit->tree, o.branch1 = new->name;
new->name, "local", &result); o.branch2 = "local";
merge_trees(&o, new->commit->tree, work,
old->commit->tree, &result);
ret = reset_tree(new->commit->tree, opts, 0); ret = reset_tree(new->commit->tree, opts, 0);
if (ret) if (ret)
return ret; return ret;

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@
#include "color.h" #include "color.h"
#include "rerere.h" #include "rerere.h"
#include "help.h" #include "help.h"
#include "merge-recursive.h"
#define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1) #define DEFAULT_OCTOPUS (1<<1)
@ -547,28 +548,65 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
struct commit_list *j; struct commit_list *j;
struct strbuf buf; struct strbuf buf;
args = xmalloc((4 + commit_list_count(common) + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
commit_list_count(remoteheads)) * sizeof(char *)); int clean;
strbuf_init(&buf, 0); struct commit *result;
strbuf_addf(&buf, "merge-%s", strategy); struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
args[i++] = buf.buf; int index_fd;
for (j = common; j; j = j->next) struct commit_list *reversed = NULL;
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); struct merge_options o;
args[i++] = "--";
args[i++] = head_arg; if (remoteheads->next) {
for (j = remoteheads; j; j = j->next) error("Not handling anything other than two heads merge.");
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); return 2;
args[i] = NULL; }
ret = run_command_v_opt(args, RUN_GIT_CMD);
strbuf_release(&buf); init_merge_options(&o);
i = 1; if (!strcmp(strategy, "subtree"))
for (j = common; j; j = j->next) o.subtree_merge = 1;
free((void *)args[i++]);
i += 2; o.branch1 = head_arg;
for (j = remoteheads; j; j = j->next) o.branch2 = remoteheads->item->util;
free((void *)args[i++]);
free(args); for (j = common; j; j = j->next)
return -ret; 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());
rollback_lock_file(lock);
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, static void count_diff_files(struct diff_queue_struct *q,
@ -779,10 +817,6 @@ static int evaluate_result(void)
int cnt = 0; int cnt = 0;
struct rev_info rev; struct rev_info rev;
discard_cache();
if (read_cache() < 0)
die("failed to read the cache");
/* Check how many files differ. */ /* Check how many files differ. */
init_revisions(&rev, ""); init_revisions(&rev, "");
setup_revisions(0, NULL, &rev, NULL); setup_revisions(0, NULL, &rev, NULL);
@ -916,12 +950,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
struct object *o; struct object *o;
struct commit *commit;
o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
if (!o) if (!o)
die("%s - not something we can merge", argv[i]); die("%s - not something we can merge", argv[i]);
remotes = &commit_list_insert(lookup_commit(o->sha1), commit = lookup_commit(o->sha1);
remotes)->next; commit->util = (void *)argv[i];
remotes = &commit_list_insert(commit, remotes)->next;
strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
setenv(buf.buf, argv[i], 1); setenv(buf.buf, argv[i], 1);
@ -1115,7 +1151,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
} }
/* Automerge succeeded. */ /* Automerge succeeded. */
discard_cache();
write_tree_trivial(result_tree); write_tree_trivial(result_tree);
automerge_was_ok = 1; automerge_was_ok = 1;
break; break;

View file

@ -12,6 +12,7 @@
#include "diff.h" #include "diff.h"
#include "revision.h" #include "revision.h"
#include "rerere.h" #include "rerere.h"
#include "merge-recursive.h"
/* /*
* This implements the builtins revert and cherry-pick. * 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)); 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 *help_msg(const unsigned char *sha1)
{ {
static char helpbuf[1024]; static char helpbuf[1024];
@ -263,14 +234,27 @@ static int index_is_dirty(void)
return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); 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) static int revert_or_cherry_pick(int argc, const char **argv)
{ {
unsigned char head[20]; unsigned char head[20];
struct commit *base, *next, *parent; struct commit *base, *next, *parent;
int i; int i, index_fd, clean;
char *oneline, *reencoded_message = NULL; char *oneline, *reencoded_message = NULL;
const char *message, *encoding; const char *message, *encoding;
const char *defmsg = xstrdup(git_path("MERGE_MSG")); 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); git_config(git_default_config, NULL);
me = action == REVERT ? "revert" : "cherry-pick"; 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) if (action == REVERT && !no_replay)
die("revert is incompatible with replay"); die("revert is incompatible with replay");
if (read_cache() < 0)
die("git %s: failed to read the index", me);
if (no_commit) { if (no_commit) {
/* /*
* We do not intend to commit immediately. We just want to * 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 { } else {
if (get_sha1("HEAD", head)) if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD"); die ("You do not have a valid HEAD");
if (read_cache() < 0)
die("could not read the index");
if (index_is_dirty()) if (index_is_dirty())
die ("Dirty index: cannot %s", me); die ("Dirty index: cannot %s", me);
discard_cache();
} }
discard_cache();
index_fd = hold_locked_index(&index_lock, 1);
if (!commit->parents) { if (!commit->parents) {
if (action == REVERT) 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", die ("Cannot get commit message for %s",
sha1_to_hex(commit->object.sha1)); 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 * "commit" is an existing commit. We would want to apply
* the difference it introduces since its first parent "prev" * 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 ? read_cache();
NULL : sha1_to_hex(base->object.sha1), init_merge_options(&o);
sha1_to_hex(head), "HEAD", o.branch1 = "HEAD";
sha1_to_hex(next->object.sha1), oneline) || o.branch2 = oneline;
write_cache_as_tree(head, 0, NULL)) {
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"); add_to_msg("\nConflicts:\n\n");
read_cache();
for (i = 0; i < active_nr;) { for (i = 0; i < active_nr;) {
struct cache_entry *ce = active_cache[i++]; struct cache_entry *ce = active_cache[i++];
if (ce_stage(ce)) { if (ce_stage(ce)) {

1376
merge-recursive.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,48 @@
#ifndef MERGE_RECURSIVE_H #ifndef MERGE_RECURSIVE_H
#define MERGE_RECURSIVE_H #define MERGE_RECURSIVE_H
int merge_recursive(struct commit *h1, #include "string-list.h"
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;
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 */
int merge_recursive(struct merge_options *o,
struct commit *h1,
struct commit *h2, struct commit *h2,
const char *branch1,
const char *branch2,
struct commit_list *ancestors, struct commit_list *ancestors,
struct commit **result); 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 *merge,
struct tree *common, struct tree *common,
const char *branch1,
const char *branch2,
struct tree **result); struct tree **result);
struct tree *write_tree_from_memory(void); /*
* "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 #endif

View file

@ -142,4 +142,26 @@ test_expect_success 'custom merge backend' '
rm -f $o $a $b 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 test_done