Merge branch 'mv/merge-in-c'

* mv/merge-in-c:
  reduce_heads(): protect from duplicate input
  reduce_heads(): thinkofix
  Add a new test for git-merge-resolve
  t6021: add a new test for git-merge-resolve
  Teach merge.log to "git-merge" again
  Build in merge
  Fix t7601-merge-pull-config.sh on AIX
  git-commit-tree: make it usable from other builtins
  Add new test case to ensure git-merge prepends the custom merge message
  Add new test case to ensure git-merge reduces octopus parents when possible
  Introduce reduce_heads()
  Introduce get_merge_bases_many()
  Add new test to ensure git-merge handles more than 25 refs.
  Introduce get_octopus_merge_bases() in commit.c
  git-fmt-merge-msg: make it usable from other builtins
  Move read_cache_unmerged() to read-cache.c
  Add new test to ensure git-merge handles pull.twohead and pull.octopus
  Move parse-options's skip_prefix() to git-compat-util.h
  Move commit_list_count() to commit.c
  Move split_cmdline() to alias.c

Conflicts:
	Makefile
	parse-options.c
This commit is contained in:
Junio C Hamano 2008-07-15 19:09:46 -07:00
commit fcab40a389
24 changed files with 1941 additions and 244 deletions

View File

@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh SCRIPT_SH += git-pull.sh
@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o BUILTIN_OBJS += builtin-mailsplit.o
BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o BUILTIN_OBJS += builtin-merge-ours.o

54
alias.c
View File

@ -21,3 +21,57 @@ char *alias_lookup(const char *alias)
git_config(alias_lookup_cb, NULL); git_config(alias_lookup_cb, NULL);
return alias_val; return alias_val;
} }
int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
char quoted = 0;
*argv = xmalloc(sizeof(char*) * size);
/* split alias_string */
(*argv)[count++] = cmdline;
for (src = dst = 0; cmdline[src];) {
char c = cmdline[src];
if (!quoted && isspace(c)) {
cmdline[dst++] = 0;
while (cmdline[++src]
&& isspace(cmdline[src]))
; /* skip */
if (count >= size) {
size += 16;
*argv = xrealloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if (!quoted && (c == '\'' || c == '"')) {
quoted = c;
src++;
} else if (c == quoted) {
quoted = 0;
src++;
} else {
if (c == '\\' && quoted != '\'') {
src++;
c = cmdline[src];
if (!c) {
free(*argv);
*argv = NULL;
return error("cmdline ends with \\");
}
}
cmdline[dst++] = c;
src++;
}
}
cmdline[dst] = 0;
if (quoted) {
free(*argv);
*argv = NULL;
return error("unclosed quote");
}
return count;
}

View File

@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
"You may want to amend it after fixing the message, or set the config\n" "You may want to amend it after fixing the message, or set the config\n"
"variable i18n.commitencoding to the encoding your project uses.\n"; "variable i18n.commitencoding to the encoding your project uses.\n";
int cmd_commit_tree(int argc, const char **argv, const char *prefix) int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret)
{ {
int i;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer;
int encoding_is_utf8; int encoding_is_utf8;
struct strbuf buffer;
git_config(git_default_config, NULL); check_valid(tree, OBJ_TREE);
if (argc < 2)
usage(commit_tree_usage);
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
check_valid(tree_sha1, OBJ_TREE);
for (i = 2; i < argc; i += 2) {
unsigned char sha1[20];
const char *a, *b;
a = argv[i]; b = argv[i+1];
if (!b || strcmp(a, "-p"))
usage(commit_tree_usage);
if (get_sha1(b, sha1))
die("Not a valid object name %s", b);
check_valid(sha1, OBJ_COMMIT);
new_parent(lookup_commit(sha1), &parents);
}
/* Not having i18n.commitencoding is the same as having utf-8 */ /* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1)); strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
/* /*
* NOTE! This ordering means that the same exact tree merged with a * NOTE! This ordering means that the same exact tree merged with a
@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addch(&buffer, '\n'); strbuf_addch(&buffer, '\n');
/* And add the comment */ /* And add the comment */
if (strbuf_read(&buffer, 0, 0) < 0) strbuf_addstr(&buffer, msg);
die("git-commit-tree: read returned %s", strerror(errno));
/* And check the encoding */ /* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf)) if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn); fprintf(stderr, commit_utf8_warn);
if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) { return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
}
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
int i;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT;
git_config(git_default_config, NULL);
if (argc < 2)
usage(commit_tree_usage);
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
for (i = 2; i < argc; i += 2) {
unsigned char sha1[20];
const char *a, *b;
a = argv[i]; b = argv[i+1];
if (!b || strcmp(a, "-p"))
usage(commit_tree_usage);
if (get_sha1(b, sha1))
die("Not a valid object name %s", b);
check_valid(sha1, OBJ_COMMIT);
new_parent(lookup_commit(sha1), &parents);
}
if (strbuf_read(&buffer, 0, 0) < 0)
die("git-commit-tree: read returned %s", strerror(errno));
if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
printf("%s\n", sha1_to_hex(commit_sha1)); printf("%s\n", sha1_to_hex(commit_sha1));
return 0; return 0;
} }

View File

@ -159,23 +159,24 @@ static int handle_line(char *line)
} }
static void print_joined(const char *singular, const char *plural, static void print_joined(const char *singular, const char *plural,
struct list *list) struct list *list, struct strbuf *out)
{ {
if (list->nr == 0) if (list->nr == 0)
return; return;
if (list->nr == 1) { if (list->nr == 1) {
printf("%s%s", singular, list->list[0]); strbuf_addf(out, "%s%s", singular, list->list[0]);
} else { } else {
int i; int i;
printf("%s", plural); strbuf_addstr(out, plural);
for (i = 0; i < list->nr - 1; i++) for (i = 0; i < list->nr - 1; i++)
printf("%s%s", i > 0 ? ", " : "", list->list[i]); strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
printf(" and %s", list->list[list->nr - 1]); strbuf_addf(out, " and %s", list->list[list->nr - 1]);
} }
} }
static void shortlog(const char *name, unsigned char *sha1, static void shortlog(const char *name, unsigned char *sha1,
struct commit *head, struct rev_info *rev, int limit) struct commit *head, struct rev_info *rev, int limit,
struct strbuf *out)
{ {
int i, count = 0; int i, count = 0;
struct commit *commit; struct commit *commit;
@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
} }
if (count > limit) if (count > limit)
printf("\n* %s: (%d commits)\n", name, count); strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else else
printf("\n* %s:\n", name); strbuf_addf(out, "\n* %s:\n", name);
for (i = 0; i < subjects.nr; i++) for (i = 0; i < subjects.nr; i++)
if (i >= limit) if (i >= limit)
printf(" ...\n"); strbuf_addf(out, " ...\n");
else else
printf(" %s\n", subjects.list[i]); strbuf_addf(out, " %s\n", subjects.list[i]);
clear_commit_marks((struct commit *)branch, flags); clear_commit_marks((struct commit *)branch, flags);
clear_commit_marks(head, flags); clear_commit_marks(head, flags);
@ -251,15 +252,105 @@ static void shortlog(const char *name, unsigned char *sha1,
free_list(&subjects); free_list(&subjects);
} }
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
{ int limit = 20, i = 0, pos = 0;
int limit = 20, i = 0;
char line[1024]; char line[1024];
FILE *in = stdin; char *p = line, *sep = "";
const char *sep = "";
unsigned char head_sha1[20]; unsigned char head_sha1[20];
const char *current_branch; const char *current_branch;
/* get current branch */
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!current_branch)
die("No current branch");
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
/* get a line */
while (pos < in->len) {
int len;
char *newline;
p = in->buf + pos;
newline = strchr(p, '\n');
len = newline ? newline - p : strlen(p);
pos += len + !!newline;
i++;
p[len] = 0;
if (handle_line(p))
die ("Error in line %d: %.*s", i, len, p);
}
strbuf_addstr(out, "Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
strbuf_addstr(out, sep);
sep = "; ";
if (src_data->head_status == 1) {
strbuf_addstr(out, srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
strbuf_addstr(out, "HEAD");
}
if (src_data->branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("branch ", "branches ", &src_data->branch,
out);
}
if (src_data->r_branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
&src_data->r_branch, out);
}
if (src_data->tag.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("tag ", "tags ", &src_data->tag, out);
}
if (src_data->generic.nr) {
strbuf_addstr(out, subsep);
print_joined("commit ", "commits ", &src_data->generic,
out);
}
if (strcmp(".", srcs.list[i]))
strbuf_addf(out, " of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
strbuf_addch(out, '\n');
else
strbuf_addf(out, " into %s\n", current_branch);
if (merge_summary) {
struct commit *head;
struct rev_info rev;
head = lookup_commit(head_sha1);
init_revisions(&rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
for (i = 0; i < origins.nr; i++)
shortlog(origins.list[i], origins.payload[i],
head, &rev, limit, out);
}
return 0;
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
FILE *in = stdin;
struct strbuf input, output;
int ret;
git_config(fmt_merge_msg_config, NULL); git_config(fmt_merge_msg_config, NULL);
while (argc > 1) { while (argc > 1) {
@ -288,82 +379,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (argc > 1) if (argc > 1)
usage(fmt_merge_msg_usage); usage(fmt_merge_msg_usage);
/* get current branch */ strbuf_init(&input, 0);
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (strbuf_read(&input, fileno(in), 0) < 0)
if (!current_branch) die("could not read input file %s", strerror(errno));
die("No current branch"); strbuf_init(&output, 0);
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
while (fgets(line, sizeof(line), in)) {
i++;
if (line[0] == 0)
continue;
if (handle_line(line))
die ("Error in line %d: %s", i, line);
}
printf("Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
printf(sep);
sep = "; ";
if (src_data->head_status == 1) {
printf(srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
printf("HEAD");
}
if (src_data->branch.nr) {
printf(subsep);
subsep = ", ";
print_joined("branch ", "branches ", &src_data->branch);
}
if (src_data->r_branch.nr) {
printf(subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
&src_data->r_branch);
}
if (src_data->tag.nr) {
printf(subsep);
subsep = ", ";
print_joined("tag ", "tags ", &src_data->tag);
}
if (src_data->generic.nr) {
printf(subsep);
print_joined("commit ", "commits ", &src_data->generic);
}
if (strcmp(".", srcs.list[i]))
printf(" of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
putchar('\n');
else
printf(" into %s\n", current_branch);
if (merge_summary) {
struct commit *head;
struct rev_info rev;
head = lookup_commit(head_sha1);
init_revisions(&rev, prefix);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
for (i = 0; i < origins.nr; i++)
shortlog(origins.list[i], origins.payload[i],
head, &rev, limit);
}
/* No cleanup yet; is standalone anyway */
ret = fmt_merge_msg(merge_summary, &input, &output);
if (ret)
return ret;
printf("%s", output.buf);
return 0; return 0;
} }

View File

@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
* - *(int *)commit->object.sha1 set to the virtual id. * - *(int *)commit->object.sha1 set to the virtual id.
*/ */
static unsigned commit_list_count(const struct commit_list *l)
{
unsigned c = 0;
for (; l; l = l->next )
c++;
return c;
}
static struct commit *make_virtual_commit(struct tree *tree, const char *comment) static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
{ {
struct commit *commit = xcalloc(1, sizeof(struct commit)); struct commit *commit = xcalloc(1, sizeof(struct commit));

1156
builtin-merge.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1)
return 0; return 0;
} }
static int read_cache_unmerged(void)
{
int i;
struct cache_entry **dst;
struct cache_entry *last = NULL;
read_cache();
dst = active_cache;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce)) {
remove_name_hash(ce);
if (last && !strcmp(ce->name, last->name))
continue;
cache_tree_invalidate_path(active_cache_tree, ce->name);
last = ce;
continue;
}
*dst++ = ce;
}
active_nr = dst - active_cache;
return !!last;
}
static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
{ {
struct tree_desc desc; struct tree_desc desc;

View File

@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
return strcmp(string + len1 - len2, postfix); return strcmp(string + len1 - len2, postfix);
} }
static inline const char *skip_prefix(const char *name, const char *prefix)
{
return !name ? "" :
prefixcmp(name, prefix) ? name : name + strlen(prefix);
}
static int opt_parse_track(const struct option *opt, const char *arg, int not) static int opt_parse_track(const struct option *opt, const char *arg, int not)
{ {
struct path_list *list = opt->value; struct path_list *list = opt->value;
@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb)
info->remote = xstrdup(value); info->remote = xstrdup(value);
} else { } else {
char *space = strchr(value, ' '); char *space = strchr(value, ' ');
value = skip_prefix(value, "refs/heads/"); const char *ptr = skip_prefix(value, "refs/heads/");
if (ptr)
value = ptr;
while (space) { while (space) {
char *merge; char *merge;
merge = xstrndup(value, space - value); merge = xstrndup(value, space - value);
path_list_append(merge, &info->merge); path_list_append(merge, &info->merge);
value = skip_prefix(space + 1, "refs/heads/"); ptr = skip_prefix(space + 1, "refs/heads/");
if (ptr)
value = ptr;
else
value = space + 1;
space = strchr(value, ' '); space = strchr(value, ' ');
} }
path_list_append(xstrdup(value), &info->merge); path_list_append(xstrdup(value), &info->merge);
@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname,
refspec.dst = (char *)refname; refspec.dst = (char *)refname;
if (!remote_find_tracking(states->remote, &refspec)) { if (!remote_find_tracking(states->remote, &refspec)) {
struct path_list_item *item; struct path_list_item *item;
const char *name = skip_prefix(refspec.src, "refs/heads/"); const char *name, *ptr;
ptr = skip_prefix(refspec.src, "refs/heads/");
if (ptr)
name = ptr;
else
name = refspec.src;
/* symbolic refs pointing nowhere were handled already */ /* symbolic refs pointing nowhere were handled already */
if ((flags & REF_ISSYMREF) || if ((flags & REF_ISSYMREF) ||
unsorted_path_list_has_path(&states->tracked, unsorted_path_list_has_path(&states->tracked,
@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
struct path_list *target = &states->tracked; struct path_list *target = &states->tracked;
unsigned char sha1[20]; unsigned char sha1[20];
void *util = NULL; void *util = NULL;
const char *ptr;
if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
target = &states->new; target = &states->new;
@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
if (hashcmp(sha1, ref->new_sha1)) if (hashcmp(sha1, ref->new_sha1))
util = &states; util = &states;
} }
path_list_append(skip_prefix(ref->name, "refs/heads/"), ptr = skip_prefix(ref->name, "refs/heads/");
target)->util = util; if (!ptr)
ptr = ref->name;
path_list_append(ptr, target)->util = util;
} }
free_refs(fetch_map); free_refs(fetch_map);
@ -522,10 +530,15 @@ static int show(int argc, const char **argv)
"es" : ""); "es" : "");
for (i = 0; i < states.remote->push_refspec_nr; i++) { for (i = 0; i < states.remote->push_refspec_nr; i++) {
struct refspec *spec = states.remote->push + i; struct refspec *spec = states.remote->push + i;
const char *p = "", *q = "";
if (spec->src)
p = skip_prefix(spec->src, "refs/heads/");
if (spec->dst)
q = skip_prefix(spec->dst, "refs/heads/");
printf(" %s%s%s%s", spec->force ? "+" : "", printf(" %s%s%s%s", spec->force ? "+" : "",
skip_prefix(spec->src, "refs/heads/"), p ? p : spec->src,
spec->dst ? ":" : "", spec->dst ? ":" : "",
skip_prefix(spec->dst, "refs/heads/")); q ? q : spec->dst);
} }
printf("\n"); printf("\n");
} }

View File

@ -2,6 +2,9 @@
#define BUILTIN_H #define BUILTIN_H
#include "git-compat-util.h" #include "git-compat-util.h"
#include "strbuf.h"
#include "cache.h"
#include "commit.h"
extern const char git_version_string[]; extern const char git_version_string[];
extern const char git_usage_string[]; extern const char git_usage_string[];
@ -11,6 +14,10 @@ extern void list_common_cmds_help(void);
extern void help_unknown_cmd(const char *cmd); extern void help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int); extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file); extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
struct strbuf *out);
extern int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret);
extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@ -57,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix); extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix);

View File

@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define read_cache() read_index(&the_index) #define read_cache() read_index(&the_index)
#define read_cache_from(path) read_index_from(&the_index, (path)) #define read_cache_from(path) read_index_from(&the_index, (path))
#define read_cache_unmerged() read_index_unmerged(&the_index)
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd)) #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
#define discard_cache() discard_index(&the_index) #define discard_cache() discard_index(&the_index)
#define unmerged_cache() unmerged_index(&the_index) #define unmerged_cache() unmerged_index(&the_index)
@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
/* Initialize and use the cache information */ /* Initialize and use the cache information */
extern int read_index(struct index_state *); extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path); extern int read_index_from(struct index_state *, const char *path);
extern int read_index_unmerged(struct index_state *);
extern int write_index(const struct index_state *, int newfd); extern int write_index(const struct index_state *, int newfd);
extern int discard_index(struct index_state *); extern int discard_index(struct index_state *);
extern int unmerged_index(const struct index_state *); extern int unmerged_index(const struct index_state *);
@ -837,5 +839,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
void overlay_tree_on_cache(const char *tree_name, const char *prefix); void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias); char *alias_lookup(const char *alias);
int split_cmdline(char *cmdline, const char ***argv);
#endif /* CACHE_H */ #endif /* CACHE_H */

133
commit.c
View File

@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
return new_list; return new_list;
} }
unsigned commit_list_count(const struct commit_list *l)
{
unsigned c = 0;
for (; l; l = l->next )
c++;
return c;
}
void free_commit_list(struct commit_list *list) void free_commit_list(struct commit_list *list)
{ {
while (list) { while (list) {
@ -525,26 +533,34 @@ static struct commit *interesting(struct commit_list *list)
return NULL; return NULL;
} }
static struct commit_list *merge_bases(struct commit *one, struct commit *two) static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
{ {
struct commit_list *list = NULL; struct commit_list *list = NULL;
struct commit_list *result = NULL; struct commit_list *result = NULL;
int i;
if (one == two) for (i = 0; i < n; i++) {
/* We do not mark this even with RESULT so we do not if (one == twos[i])
/*
* We do not mark this even with RESULT so we do not
* have to clean it up. * have to clean it up.
*/ */
return commit_list_insert(one, &result); return commit_list_insert(one, &result);
}
if (parse_commit(one)) if (parse_commit(one))
return NULL; return NULL;
if (parse_commit(two)) for (i = 0; i < n; i++) {
if (parse_commit(twos[i]))
return NULL; return NULL;
}
one->object.flags |= PARENT1; one->object.flags |= PARENT1;
two->object.flags |= PARENT2;
insert_by_date(one, &list); insert_by_date(one, &list);
insert_by_date(two, &list); for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
insert_by_date(twos[i], &list);
}
while (interesting(list)) { while (interesting(list)) {
struct commit *commit; struct commit *commit;
@ -592,21 +608,53 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
return result; return result;
} }
struct commit_list *get_merge_bases(struct commit *one, struct commit_list *get_octopus_merge_bases(struct commit_list *in)
struct commit *two, int cleanup) {
struct commit_list *i, *j, *k, *ret = NULL;
struct commit_list **pptr = &ret;
for (i = in; i; i = i->next) {
if (!ret)
pptr = &commit_list_insert(i->item, pptr)->next;
else {
struct commit_list *new = NULL, *end = NULL;
for (j = ret; j; j = j->next) {
struct commit_list *bases;
bases = get_merge_bases(i->item, j->item, 1);
if (!new)
new = bases;
else
end->next = bases;
for (k = bases; k; k = k->next)
end = k;
}
ret = new;
}
}
return ret;
}
struct commit_list *get_merge_bases_many(struct commit *one,
int n,
struct commit **twos,
int cleanup)
{ {
struct commit_list *list; struct commit_list *list;
struct commit **rslt; struct commit **rslt;
struct commit_list *result; struct commit_list *result;
int cnt, i, j; int cnt, i, j;
result = merge_bases(one, two); result = merge_bases_many(one, n, twos);
if (one == two) for (i = 0; i < n; i++) {
if (one == twos[i])
return result; return result;
}
if (!result || !result->next) { if (!result || !result->next) {
if (cleanup) { if (cleanup) {
clear_commit_marks(one, all_flags); clear_commit_marks(one, all_flags);
clear_commit_marks(two, all_flags); for (i = 0; i < n; i++)
clear_commit_marks(twos[i], all_flags);
} }
return result; return result;
} }
@ -624,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one,
free_commit_list(result); free_commit_list(result);
clear_commit_marks(one, all_flags); clear_commit_marks(one, all_flags);
clear_commit_marks(two, all_flags); for (i = 0; i < n; i++)
clear_commit_marks(twos[i], all_flags);
for (i = 0; i < cnt - 1; i++) { for (i = 0; i < cnt - 1; i++) {
for (j = i+1; j < cnt; j++) { for (j = i+1; j < cnt; j++) {
if (!rslt[i] || !rslt[j]) if (!rslt[i] || !rslt[j])
continue; continue;
result = merge_bases(rslt[i], rslt[j]); result = merge_bases_many(rslt[i], 1, &rslt[j]);
clear_commit_marks(rslt[i], all_flags); clear_commit_marks(rslt[i], all_flags);
clear_commit_marks(rslt[j], all_flags); clear_commit_marks(rslt[j], all_flags);
for (list = result; list; list = list->next) { for (list = result; list; list = list->next) {
@ -651,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one,
return result; return result;
} }
struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
int cleanup)
{
return get_merge_bases_many(one, 1, &two, cleanup);
}
int in_merge_bases(struct commit *commit, struct commit **reference, int num) int in_merge_bases(struct commit *commit, struct commit **reference, int num)
{ {
struct commit_list *bases, *b; struct commit_list *bases, *b;
@ -670,3 +725,55 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
free_commit_list(bases); free_commit_list(bases);
return ret; return ret;
} }
struct commit_list *reduce_heads(struct commit_list *heads)
{
struct commit_list *p;
struct commit_list *result = NULL, **tail = &result;
struct commit **other;
size_t num_head, num_other;
if (!heads)
return NULL;
/* Avoid unnecessary reallocations */
for (p = heads, num_head = 0; p; p = p->next)
num_head++;
other = xcalloc(sizeof(*other), num_head);
/* For each commit, see if it can be reached by others */
for (p = heads; p; p = p->next) {
struct commit_list *q, *base;
/* Do we already have this in the result? */
for (q = result; q; q = q->next)
if (p->item == q->item)
break;
if (q)
continue;
num_other = 0;
for (q = heads; q; q = q->next) {
if (p->item == q->item)
continue;
other[num_other++] = q->item;
}
if (num_other)
base = get_merge_bases_many(p->item, num_other, other, 1);
else
base = NULL;
/*
* If p->item does not have anything common with other
* commits, there won't be any merge base. If it is
* reachable from some of the others, p->item will be
* the merge base. If its history is connected with
* others, but p->item is not reachable by others, we
* will get something other than p->item back.
*/
if (!base || (base->item != p->item))
tail = &(commit_list_insert(p->item, tail)->next);
free_commit_list(base);
}
free(other);
return result;
}

View File

@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
int parse_commit(struct commit *item); int parse_commit(struct commit *item);
struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
unsigned commit_list_count(const struct commit_list *l);
struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
void free_commit_list(struct commit_list *list); void free_commit_list(struct commit_list *list);
@ -120,6 +121,7 @@ int read_graft_file(const char *graft_file);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1); struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1); extern int register_shallow(const unsigned char *sha1);
extern int unregister_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1);
@ -137,4 +139,6 @@ static inline int single_parent(struct commit *commit)
return commit->parents && !commit->parents->next; return commit->parents && !commit->parents->next;
} }
struct commit_list *reduce_heads(struct commit_list *heads);
#endif /* COMMIT_H */ #endif /* COMMIT_H */

View File

@ -157,6 +157,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
extern int prefixcmp(const char *str, const char *prefix); extern int prefixcmp(const char *str, const char *prefix);
extern time_t tm_to_time_t(const struct tm *tm); extern time_t tm_to_time_t(const struct tm *tm);
static inline const char *skip_prefix(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
#ifdef NO_MMAP #ifdef NO_MMAP
#ifndef PROT_READ #ifndef PROT_READ

54
git.c
View File

@ -127,59 +127,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
return handled; return handled;
} }
static int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
char quoted = 0;
*argv = xmalloc(sizeof(char*) * size);
/* split alias_string */
(*argv)[count++] = cmdline;
for (src = dst = 0; cmdline[src];) {
char c = cmdline[src];
if (!quoted && isspace(c)) {
cmdline[dst++] = 0;
while (cmdline[++src]
&& isspace(cmdline[src]))
; /* skip */
if (count >= size) {
size += 16;
*argv = xrealloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if(!quoted && (c == '\'' || c == '"')) {
quoted = c;
src++;
} else if (c == quoted) {
quoted = 0;
src++;
} else {
if (c == '\\' && quoted != '\'') {
src++;
c = cmdline[src];
if (!c) {
free(*argv);
*argv = NULL;
return error("cmdline ends with \\");
}
}
cmdline[dst++] = c;
src++;
}
}
cmdline[dst] = 0;
if (quoted) {
free(*argv);
*argv = NULL;
return error("unclosed quote");
}
return count;
}
static int handle_alias(int *argcp, const char ***argv) static int handle_alias(int *argcp, const char ***argv)
{ {
int envchanged = 0, ret = 0, saved_errno = errno; int envchanged = 0, ret = 0, saved_errno = errno;
@ -366,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote }, { "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo }, { "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit }, { "mailsplit", cmd_mailsplit },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file }, { "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP }, { "merge-ours", cmd_merge_ours, RUN_SETUP },

View File

@ -5,12 +5,6 @@
#define OPT_SHORT 1 #define OPT_SHORT 1
#define OPT_UNSET 2 #define OPT_UNSET 2
static inline const char *skip_prefix(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int opterror(const struct option *opt, const char *reason, int flags) static int opterror(const struct option *opt, const char *reason, int flags)
{ {
if (flags & OPT_SHORT) if (flags & OPT_SHORT)

View File

@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd)
} }
return ce_flush(&c, newfd); return ce_flush(&c, newfd);
} }
/*
* Read the index file that is potentially unmerged into given
* index_state, dropping any unmerged entries. Returns true is
* the index is unmerged. Callers who want to refuse to work
* from an unmerged state can call this and check its return value,
* instead of calling read_cache().
*/
int read_index_unmerged(struct index_state *istate)
{
int i;
struct cache_entry **dst;
struct cache_entry *last = NULL;
read_index(istate);
dst = istate->cache;
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (ce_stage(ce)) {
remove_name_hash(ce);
if (last && !strcmp(ce->name, last->name))
continue;
cache_tree_invalidate_path(istate->cache_tree, ce->name);
last = ce;
continue;
}
*dst++ = ce;
}
istate->cache_nr = dst - istate->cache;
return !!last;
}

View File

@ -89,4 +89,8 @@ EOF
test_expect_success 'Criss-cross merge result' 'cmp file file-expect' test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
test_expect_success 'Criss-cross merge fails (-s resolve)' \
'git reset --hard A^ &&
test_must_fail git merge -s resolve -m "final merge" B'
test_done test_done

View File

@ -465,11 +465,51 @@ test_expect_success 'merge log message' '
git merge --no-log c2 && git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act && git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.nolog msg.act "[OOPS] bad merge log message" && verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
git merge --log c3 && git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act && git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
git reset --hard HEAD^ &&
git config merge.log yes &&
git merge c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.log msg.act "[OOPS] bad merge log message" verify_diff msg.log msg.act "[OOPS] bad merge log message"
' '
test_debug 'gitk --all' test_debug 'gitk --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_expect_success 'merge c1 with c1 and c2' '
git reset --hard c1 &&
git config branch.master.mergeoptions "" &&
test_tick &&
git merge c1 c2 &&
verify_merge file result.1-5 &&
verify_parents $c1 $c2
'
test_debug 'gitk --all'
test_done test_done

129
t/t7601-merge-pull-config.sh Executable file
View File

@ -0,0 +1,129 @@
#!/bin/sh
test_description='git-merge
Testing pull.* configuration parsing.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 >c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 >c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 >c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 >c3.c &&
git add c3.c &&
git commit -m c3 &&
git tag c3
'
test_expect_success 'merge c1 with c2' '
git reset --hard c1 &&
test -f c0.c &&
test -f c1.c &&
test ! -f c2.c &&
test ! -f c3.c &&
git merge c2 &&
test -f c1.c &&
test -f c2.c
'
test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
git reset --hard c1 &&
git config pull.twohead ours &&
git merge c2 &&
test -f c1.c &&
! test -f c2.c
'
test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
git reset --hard c1 &&
git config pull.octopus "recursive" &&
test_must_fail git merge c2 c3 &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
'
test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
git reset --hard c1 &&
git config pull.octopus "recursive octopus" &&
git merge c2 c3 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c &&
test -f c3.c
'
conflict_count()
{
{
git diff-files --name-only
git ls-files --unmerged
} | wc -l
}
# c4 - c5
# \ c6
#
# There are two conflicts here:
#
# 1) Because foo.c is renamed to bar.c, recursive will handle this,
# resolve won't.
#
# 2) One in conflict.c and that will always fail.
test_expect_success 'setup conflicted merge' '
git reset --hard c0 &&
echo A >conflict.c &&
git add conflict.c &&
echo contents >foo.c &&
git add foo.c &&
git commit -m c4 &&
git tag c4 &&
echo B >conflict.c &&
git add conflict.c &&
git mv foo.c bar.c &&
git commit -m c5 &&
git tag c5 &&
git reset --hard c4 &&
echo C >conflict.c &&
git add conflict.c &&
echo secondline >> foo.c &&
git add foo.c &&
git commit -m c6 &&
git tag c6
'
# First do the merge with resolve and recursive then verify that
# recusive is choosen.
test_expect_success 'merge picks up the best result' '
git config pull.twohead "recursive resolve" &&
git reset --hard c5 &&
git merge -s resolve c6
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
git merge -s recursive c6
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
git merge c6
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
'
test_done

52
t/t7602-merge-octopus-many.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/sh
test_description='git-merge
Testing octopus merge with more than 25 refs.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
i=1 &&
while test $i -le 30
do
git reset --hard c0 &&
echo c$i > c$i.c &&
git add c$i.c &&
git commit -m c$i &&
git tag c$i &&
i=`expr $i + 1` || return 1
done
'
test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
git reset --hard c1 &&
i=2 &&
refs="" &&
while test $i -le 30
do
refs="$refs c$i"
i=`expr $i + 1`
done
git merge $refs &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
i=1 &&
while test $i -le 30
do
test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
i=`expr $i + 1` || return 1
done &&
git diff --exit-code &&
i=1 &&
while test $i -le 30
do
test -f c$i.c &&
i=`expr $i + 1` || return 1
done
'
test_done

63
t/t7603-merge-reduce-heads.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/sh
test_description='git-merge
Testing octopus merge when reducing parents to independent branches.'
. ./test-lib.sh
# 0 - 1
# \ 2
# \ 3
# \ 4 - 5
#
# So 1, 2, 3 and 5 should be kept, 4 should be avoided.
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 > c3.c &&
git add c3.c &&
git commit -m c3 &&
git tag c3 &&
git reset --hard c0 &&
echo c4 > c4.c &&
git add c4.c &&
git commit -m c4 &&
git tag c4 &&
echo c5 > c5.c &&
git add c5.c &&
git commit -m c5 &&
git tag c5
'
test_expect_success 'merge c1 with c2, c3, c4, c5' '
git reset --hard c1 &&
git merge c2 c3 c4 c5 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c &&
test -f c3.c &&
test -f c4.c &&
test -f c5.c
'
test_done

37
t/t7604-merge-custom-message.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/sh
test_description='git-merge
Testing merge when using a custom message for the merge commit.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2
'
cat >expected <<\EOF
custom message
Merge commit 'c2'
EOF
test_expect_success 'merge c2 with a custom message' '
git reset --hard c1 &&
git merge -m "custom message" c2 &&
git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
test_cmp expected actual
'
test_done

46
t/t7605-merge-resolve.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/sh
test_description='git-merge
Testing the resolve strategy.'
. ./test-lib.sh
test_expect_success 'setup' '
echo c0 > c0.c &&
git add c0.c &&
git commit -m c0 &&
git tag c0 &&
echo c1 > c1.c &&
git add c1.c &&
git commit -m c1 &&
git tag c1 &&
git reset --hard c0 &&
echo c2 > c2.c &&
git add c2.c &&
git commit -m c2 &&
git tag c2 &&
git reset --hard c0 &&
echo c3 > c2.c &&
git add c2.c &&
git commit -m c3 &&
git tag c3
'
test_expect_success 'merge c1 to c2' '
git reset --hard c1 &&
git merge -s resolve c2 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
test -f c2.c
'
test_expect_success 'merge c2 to c3 (fails)' '
git reset --hard c2 &&
test_must_fail git merge -s resolve c3
'
test_done