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:
commit
ed520a8f27
1
Makefile
1
Makefile
@ -443,6 +443,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
|
||||
|
@ -293,6 +293,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);
|
||||
@ -311,13 +312,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;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
@ -547,28 +548,65 @@ 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());
|
||||
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,
|
||||
@ -779,10 +817,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);
|
||||
@ -916,12 +950,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);
|
||||
@ -1115,7 +1151,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;
|
||||
|
@ -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)) {
|
||||
|
1376
merge-recursive.c
Normal file
1376
merge-recursive.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,48 @@
|
||||
#ifndef 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,
|
||||
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);
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user