alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
- } while(0)
+ } while (0)
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+ extern char *git_path_submodule(const char *path, const char *fmt, ...)
+ __attribute__((format (printf, 2, 3)));
+
extern char *sha1_file_name(const unsigned char *sha1);
extern char *sha1_pack_name(const unsigned char *sha1);
extern char *sha1_pack_index_name(const unsigned char *sha1);
char *timebuf,
size_t timebuf_size);
int parse_date(const char *date, char *buf, int bufsize);
+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
void datestamp(char *buf, int bufsize);
#define approxidate(s) approxidate_careful((s), NULL)
unsigned long approxidate_careful(const char *, int *);
#include "attr.h"
#include "merge-recursive.h"
#include "dir.h"
+ #include "submodule.h"
static struct tree *shift_tree_object(struct tree *one, struct tree *two,
const char *subtree_shift)
if (parse_commit(commit) != 0)
printf("(bad commit)\n");
else {
- const char *s;
- int len;
- for (s = commit->buffer; *s; s++)
- if (*s == '\n' && s[1] == '\n') {
- s += 2;
- break;
- }
- for (len = 0; s[len] && '\n' != s[len]; len++)
- ; /* do nothing */
- printf("%.*s\n", len, s);
+ const char *title;
+ int len = find_commit_subject(commit->buffer, &title);
+ if (len)
+ printf("%.*s\n", len, title);
}
}
}
opts.fn = threeway_merge;
opts.src_index = &the_index;
opts.dst_index = &the_index;
- opts.msgs = get_porcelain_error_msgs();
+ set_porcelain_error_msgs(opts.msgs, "merge");
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
void *buf;
unsigned long size;
- if (S_ISGITLINK(mode))
+ if (S_ISGITLINK(mode)) {
/*
* We may later decide to recursively descend into
* the submodule directory and update its index
* and/or work tree, but we do not do that now.
*/
+ update_wd = 0;
goto update_index;
+ }
buf = read_sha1_file(sha, &type, &size);
if (!buf)
free(result_buf.ptr);
result.clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
- result.clean = 0;
- hashcpy(result.sha, a->sha1);
+ result.clean = merge_submodule(result.sha, one->path, one->sha1,
+ a->sha1, b->sha1);
} else if (S_ISLNK(a->mode)) {
hashcpy(result.sha, a->sha1);
struct string_list *b_renames)
{
int clean_merge = 1, i, j;
- struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+ struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
+ struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
const struct rename *sre;
for (i = 0; i < a_renames->nr; i++) {
return clean_merge;
}
-struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
+void set_porcelain_error_msgs(const char **msgs, const char *cmd)
{
- struct unpack_trees_error_msgs msgs = {
- /* would_overwrite */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_file */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_dir */
- "Updating '%s' would lose untracked files in it. Aborting.",
- /* would_lose_untracked */
- "Untracked working tree file '%s' would be %s by merge. Aborting",
- /* bind_overlap -- will not happen here */
- NULL,
- };
- if (advice_commit_before_merge) {
- msgs.would_overwrite = msgs.not_uptodate_file =
- "Your local changes to '%s' would be overwritten by merge. Aborting.\n"
- "Please, commit your changes or stash them before you can merge.";
- }
- return msgs;
+ const char *msg;
+ char *tmp;
+ const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
+ if (advice_commit_before_merge)
+ msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
+ "Please, commit your changes or stash them before you can %s.";
+ else
+ msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
+ sprintf(tmp, msg, cmd, cmd2);
+ msgs[ERROR_WOULD_OVERWRITE] = tmp;
+ msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
+
+ msgs[ERROR_NOT_UPTODATE_DIR] =
+ "Updating the following directories would lose untracked files in it:\n%s";
+
+ if (advice_commit_before_merge)
+ msg = "The following untracked working tree files would be %s by %s:\n%%s"
+ "Please move or remove them before you can %s.";
+ else
+ msg = "The following untracked working tree files would be %s by %s:\n%%s";
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
+ sprintf(tmp, msg, "removed", cmd, cmd2);
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
+ sprintf(tmp, msg, "overwritten", cmd, cmd2);
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
+
+ /*
+ * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
+ * cannot easily display it as a list.
+ */
+ msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind.";
+
+ msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
+ "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
+ msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
+ "The following Working tree files would be overwritten by sparse checkout update:\n%s";
+ msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
+ "The following Working tree files would be removed by sparse checkout update:\n%s";
}
int merge_trees(struct merge_options *o,
}
if (sha_eq(common->object.sha1, merge->object.sha1)) {
- output(o, 0, "Already uptodate!");
+ output(o, 0, "Already up-to-date!");
*result = head;
return 1;
}
return cleanup_path(pathname);
}
+ char *git_path_submodule(const char *path, const char *fmt, ...)
+ {
+ char *pathname = get_pathname();
+ struct strbuf buf = STRBUF_INIT;
+ const char *git_dir;
+ va_list args;
+ unsigned len;
+
+ len = strlen(path);
+ if (len > PATH_MAX-100)
+ return bad_path;
+
+ strbuf_addstr(&buf, path);
+ if (len && path[len-1] != '/')
+ strbuf_addch(&buf, '/');
+ strbuf_addstr(&buf, ".git");
+
+ git_dir = read_gitfile_gently(buf.buf);
+ if (git_dir) {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, git_dir);
+ }
+ strbuf_addch(&buf, '/');
+
+ if (buf.len >= PATH_MAX)
+ return bad_path;
+ memcpy(pathname, buf.buf, buf.len + 1);
+
+ strbuf_release(&buf);
+ len = strlen(pathname);
+
+ va_start(args, fmt);
+ len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
+ }
/* git_mkstemp() - create tmp file honoring TMPDIR variable */
int git_mkstemp(char *path, size_t len, const char *template)
size_t username_len = first_slash - username;
if (username_len == 0) {
const char *home = getenv("HOME");
+ if (!home)
+ goto return_null;
strbuf_add(&user_path, home, strlen(home));
} else {
struct passwd *pw = getpw_str(username, username_len);
char did_packed;
struct ref_list *loose;
struct ref_list *packed;
- } cached_refs;
+ } cached_refs, submodule_refs;
static struct ref_list *current_ref;
static struct ref_list *extra_refs;
extra_refs = NULL;
}
- static struct ref_list *get_packed_refs(void)
+ static struct ref_list *get_packed_refs(const char *submodule)
{
- if (!cached_refs.did_packed) {
- FILE *f = fopen(git_path("packed-refs"), "r");
- cached_refs.packed = NULL;
+ const char *packed_refs_file;
+ struct cached_refs *refs;
+
+ if (submodule) {
+ packed_refs_file = git_path_submodule(submodule, "packed-refs");
+ refs = &submodule_refs;
+ free_ref_list(refs->packed);
+ } else {
+ packed_refs_file = git_path("packed-refs");
+ refs = &cached_refs;
+ }
+
+ if (!refs->did_packed || submodule) {
+ FILE *f = fopen(packed_refs_file, "r");
+ refs->packed = NULL;
if (f) {
- read_packed_refs(f, &cached_refs);
+ read_packed_refs(f, refs);
fclose(f);
}
- cached_refs.did_packed = 1;
+ refs->did_packed = 1;
}
- return cached_refs.packed;
+ return refs->packed;
}
- static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+ static struct ref_list *get_ref_dir(const char *submodule, const char *base,
+ struct ref_list *list)
{
- DIR *dir = opendir(git_path("%s", base));
+ DIR *dir;
+ const char *path;
+
+ if (submodule)
+ path = git_path_submodule(submodule, "%s", base);
+ else
+ path = git_path("%s", base);
+
+
+ dir = opendir(path);
if (dir) {
struct dirent *de;
struct stat st;
int flag;
int namelen;
+ const char *refdir;
if (de->d_name[0] == '.')
continue;
if (has_extension(de->d_name, ".lock"))
continue;
memcpy(ref + baselen, de->d_name, namelen+1);
- if (stat(git_path("%s", ref), &st) < 0)
+ refdir = submodule
+ ? git_path_submodule(submodule, "%s", ref)
+ : git_path("%s", ref);
+ if (stat(refdir, &st) < 0)
continue;
if (S_ISDIR(st.st_mode)) {
- list = get_ref_dir(ref, list);
+ list = get_ref_dir(submodule, ref, list);
continue;
}
- if (!resolve_ref(ref, sha1, 1, &flag)) {
+ if (submodule) {
hashclr(sha1);
- flag |= REF_BROKEN;
- }
+ flag = 0;
+ if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
+ hashclr(sha1);
+ flag |= REF_BROKEN;
+ }
+ } else
+ if (!resolve_ref(ref, sha1, 1, &flag)) {
+ hashclr(sha1);
+ flag |= REF_BROKEN;
+ }
list = add_ref(ref, sha1, flag, list, NULL);
}
free(ref);
for_each_rawref(warn_if_dangling_symref, &data);
}
- static struct ref_list *get_loose_refs(void)
+ static struct ref_list *get_loose_refs(const char *submodule)
{
+ if (submodule) {
+ free_ref_list(submodule_refs.loose);
+ submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
+ return submodule_refs.loose;
+ }
+
if (!cached_refs.did_loose) {
- cached_refs.loose = get_ref_dir("refs", NULL);
+ cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
cached_refs.did_loose = 1;
}
return cached_refs.loose;
git_snpath(path, sizeof(path), "%s", ref);
/* Special case: non-existing file. */
if (lstat(path, &st) < 0) {
- struct ref_list *list = get_packed_refs();
+ struct ref_list *list = get_packed_refs(NULL);
while (list) {
if (!strcmp(ref, list->name)) {
hashcpy(sha1, list->sha1);
return -1;
if ((flag & REF_ISPACKED)) {
- struct ref_list *list = get_packed_refs();
+ struct ref_list *list = get_packed_refs(NULL);
while (list) {
if (!strcmp(list->name, ref)) {
return -1;
}
- static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data)
+ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
+ int trim, int flags, void *cb_data)
{
int retval = 0;
- struct ref_list *packed = get_packed_refs();
- struct ref_list *loose = get_loose_refs();
+ struct ref_list *packed = get_packed_refs(submodule);
+ struct ref_list *loose = get_loose_refs(submodule);
struct ref_list *extra;
return retval;
}
- int head_ref(each_ref_fn fn, void *cb_data)
+
+ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
int flag;
+ if (submodule) {
+ if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0)
+ return fn("HEAD", sha1, 0, cb_data);
+
+ return 0;
+ }
+
if (resolve_ref("HEAD", sha1, 1, &flag))
return fn("HEAD", sha1, flag, cb_data);
+
return 0;
}
+ int head_ref(each_ref_fn fn, void *cb_data)
+ {
+ return do_head_ref(NULL, fn, cb_data);
+ }
+
+ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+ {
+ return do_head_ref(submodule, fn, cb_data);
+ }
+
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0, 0, cb_data);
+ return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
+ }
+
+ int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+ {
+ return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
}
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+ }
+
+ int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+ each_ref_fn fn, void *cb_data)
+ {
+ return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
return for_each_ref_in("refs/tags/", fn, cb_data);
}
+ int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+ {
+ return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
+ }
+
int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
return for_each_ref_in("refs/heads/", fn, cb_data);
}
+ int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+ {
+ return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
+ }
+
int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
return for_each_ref_in("refs/remotes/", fn, cb_data);
}
+ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+ {
+ return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
+ }
+
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
+ return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
}
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0,
+ return do_for_each_ref(NULL, "refs/", fn, 0,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(ref, NULL, get_packed_refs(), 0)) {
+ !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
last_errno = ENOTDIR;
goto error_return;
}
int fd;
int found = 0;
- packed_ref_list = get_packed_refs();
+ packed_ref_list = get_packed_refs(NULL);
for (list = packed_ref_list; list; list = list->next) {
if (!strcmp(refname, list->name)) {
found = 1;
return ret;
}
+/*
+ * People using contrib's git-new-workdir have .git/logs/refs ->
+ * /some/other/path/.git/logs/refs, and that may live on another device.
+ *
+ * IOW, to avoid cross device rename errors, the temporary renamed log must
+ * live into logs/refs.
+ */
+#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
+
int rename_ref(const char *oldref, const char *newref, const char *logmsg)
{
static const char renamed_ref[] = "RENAMED-REF";
if (!symref)
return error("refname %s not found", oldref);
- if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+ if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
return 1;
- if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+ if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
return 1;
lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
if (write_ref_sha1(lock, orig_sha1, logmsg))
return error("unable to save current sha1 in %s", renamed_ref);
- if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
- return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+ if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+ return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
oldref, strerror(errno));
if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
}
retry:
- if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+ if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
if (errno==EISDIR || errno==ENOTDIR) {
/*
* rename(a, b) when b is an existing
}
goto retry;
} else {
- error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+ error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
newref, strerror(errno));
goto rollback;
}
error("unable to restore logfile %s from %s: %s",
oldref, newref, strerror(errno));
if (!logmoved && log &&
- rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
- error("unable to restore logfile %s from tmp-renamed-log: %s",
+ rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+ error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
oldref, strerror(errno));
return 1;
#include "revision.h"
#include "run-command.h"
#include "diffcore.h"
+ #include "refs.h"
+#include "string-list.h"
+
+struct string_list config_name_for_path;
+struct string_list config_ignore_for_name;
static int add_submodule_odb(const char *path)
{
return ret;
}
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+ const char *path)
+{
+ struct string_list_item *path_option, *ignore_option;
+ path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+ if (path_option) {
+ ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
+ if (ignore_option)
+ handle_ignore_submodules_arg(diffopt, ignore_option->util);
+ }
+}
+
+static int submodule_config(const char *var, const char *value, void *cb)
+{
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+ return 0;
+}
+
+void gitmodules_config(void)
+{
+ const char *work_tree = get_git_work_tree();
+ if (work_tree) {
+ struct strbuf gitmodules_path = STRBUF_INIT;
+ strbuf_addstr(&gitmodules_path, work_tree);
+ strbuf_addstr(&gitmodules_path, "/.gitmodules");
+ git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+ strbuf_release(&gitmodules_path);
+ }
+}
+
+int parse_submodule_config_option(const char *var, const char *value)
+{
+ int len;
+ struct string_list_item *config;
+ struct strbuf submodname = STRBUF_INIT;
+
+ var += 10; /* Skip "submodule." */
+
+ len = strlen(var);
+ if ((len > 5) && !strcmp(var + len - 5, ".path")) {
+ strbuf_add(&submodname, var, len - 5);
+ config = unsorted_string_list_lookup(&config_name_for_path, value);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_name_for_path, xstrdup(value));
+ config->util = strbuf_detach(&submodname, NULL);
+ strbuf_release(&submodname);
+ } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
+ if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
+ strcmp(value, "all") && strcmp(value, "none")) {
+ warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
+ return 0;
+ }
+
+ strbuf_add(&submodname, var, len - 7);
+ config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_ignore_for_name,
+ strbuf_detach(&submodname, NULL));
+ strbuf_release(&submodname);
+ config->util = xstrdup(value);
+ return 0;
+ }
+ return 0;
+}
+
void handle_ignore_submodules_arg(struct diff_options *diffopt,
const char *arg)
{
+ DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
+
if (!strcmp(arg, "all"))
DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
else if (!strcmp(arg, "untracked"))
DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
else if (!strcmp(arg, "dirty"))
DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
- else
+ else if (strcmp(arg, "none"))
die("bad --ignore-submodules argument: %s", arg);
}
strbuf_release(&buf);
return dirty_submodule;
}
+
+ static int find_first_merges(struct object_array *result, const char *path,
+ struct commit *a, struct commit *b)
+ {
+ int i, j;
+ struct object_array merges;
+ struct commit *commit;
+ int contains_another;
+
+ char merged_revision[42];
+ const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+ "--all", merged_revision, NULL };
+ struct rev_info revs;
+ struct setup_revision_opt rev_opts;
+
+ memset(&merges, 0, sizeof(merges));
+ memset(result, 0, sizeof(struct object_array));
+ memset(&rev_opts, 0, sizeof(rev_opts));
+
+ /* get all revisions that merge commit a */
+ snprintf(merged_revision, sizeof(merged_revision), "^%s",
+ sha1_to_hex(a->object.sha1));
+ init_revisions(&revs, NULL);
+ rev_opts.submodule = path;
+ setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);
+
+ /* save all revisions from the above list that contain b */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct object *o = &(commit->object);
+ if (in_merge_bases(b, &commit, 1))
+ add_object_array(o, NULL, &merges);
+ }
+
+ /* Now we've got all merges that contain a and b. Prune all
+ * merges that contain another found merge and save them in
+ * result.
+ */
+ for (i = 0; i < merges.nr; i++) {
+ struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+ contains_another = 0;
+ for (j = 0; j < merges.nr; j++) {
+ struct commit *m2 = (struct commit *) merges.objects[j].item;
+ if (i != j && in_merge_bases(m2, &m1, 1)) {
+ contains_another = 1;
+ break;
+ }
+ }
+
+ if (!contains_another)
+ add_object_array(merges.objects[i].item,
+ merges.objects[i].name, result);
+ }
+
+ free(merges.objects);
+ return result->nr;
+ }
+
+ static void print_commit(struct commit *commit)
+ {
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+ fprintf(stderr, "%s\n", sb.buf);
+ strbuf_release(&sb);
+ }
+
+ #define MERGE_WARNING(path, msg) \
+ warning("Failed to merge submodule %s (%s)", path, msg);
+
+ int merge_submodule(unsigned char result[20], const char *path,
+ const unsigned char base[20], const unsigned char a[20],
+ const unsigned char b[20])
+ {
+ struct commit *commit_base, *commit_a, *commit_b;
+ int parent_count;
+ struct object_array merges;
+
+ int i;
+
+ /* store a in result in case we fail */
+ hashcpy(result, a);
+
+ /* we can not handle deletion conflicts */
+ if (is_null_sha1(base))
+ return 0;
+ if (is_null_sha1(a))
+ return 0;
+ if (is_null_sha1(b))
+ return 0;
+
+ if (add_submodule_odb(path)) {
+ MERGE_WARNING(path, "not checked out");
+ return 0;
+ }
+
+ if (!(commit_base = lookup_commit_reference(base)) ||
+ !(commit_a = lookup_commit_reference(a)) ||
+ !(commit_b = lookup_commit_reference(b))) {
+ MERGE_WARNING(path, "commits not present");
+ return 0;
+ }
+
+ /* check whether both changes are forward */
+ if (!in_merge_bases(commit_base, &commit_a, 1) ||
+ !in_merge_bases(commit_base, &commit_b, 1)) {
+ MERGE_WARNING(path, "commits don't follow merge-base");
+ return 0;
+ }
+
+ /* Case #1: a is contained in b or vice versa */
+ if (in_merge_bases(commit_a, &commit_b, 1)) {
+ hashcpy(result, b);
+ return 1;
+ }
+ if (in_merge_bases(commit_b, &commit_a, 1)) {
+ hashcpy(result, a);
+ return 1;
+ }
+
+ /*
+ * Case #2: There are one or more merges that contain a and b in
+ * the submodule. If there is only one, then present it as a
+ * suggestion to the user, but leave it marked unmerged so the
+ * user needs to confirm the resolution.
+ */
+
+ /* find commit which merges them */
+ parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+ switch (parent_count) {
+ case 0:
+ MERGE_WARNING(path, "merge following commits not found");
+ break;
+
+ case 1:
+ MERGE_WARNING(path, "not fast-forward");
+ fprintf(stderr, "Found a possible merge resolution "
+ "for the submodule:\n");
+ print_commit((struct commit *) merges.objects[0].item);
+ fprintf(stderr,
+ "If this is correct simply add it to the index "
+ "for example\n"
+ "by using:\n\n"
+ " git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+ "which will accept this suggestion.\n",
+ sha1_to_hex(merges.objects[0].item->sha1), path);
+ break;
+
+ default:
+ MERGE_WARNING(path, "multiple merges found");
+ for (i = 0; i < merges.nr; i++)
+ print_commit((struct commit *) merges.objects[i].item);
+ }
+
+ free(merges.objects);
+ return 0;
+ }
struct diff_options;
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+ const char *path);
+void gitmodules_config();
+int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
const char *del, const char *add, const char *reset);
unsigned is_submodule_modified(const char *path, int ignore_untracked);
+ int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
+ const unsigned char a[20], const unsigned char b[20]);
#endif