Merge branch 'hv/submodule-find-ff-merge'
authorJunio C Hamano <gitster@pobox.com>
Sun, 22 Aug 2010 06:27:59 +0000 (23:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 22 Aug 2010 06:27:59 +0000 (23:27 -0700)
* hv/submodule-find-ff-merge:
Implement automatic fast-forward merge for submodules
setup_revisions(): Allow walking history in a submodule
Teach ref iteration module about submodules

Conflicts:
submodule.c

1  2 
cache.h
merge-recursive.c
path.c
refs.c
submodule.c
submodule.h
diff --combined cache.h
index 37ef9d8a0058bb0cc7826b2a22706064a3f6d3d7,32932e87b08da6f08f1165e70fcb1ff1ec1fb0af..94dde5312f368523447d6bd6fb61f5f0b51af25a
+++ b/cache.h
@@@ -449,7 -449,7 +449,7 @@@ extern int init_db(const char *template
                                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 *);
@@@ -641,6 -641,9 +641,9 @@@ extern char *git_pathdup(const char *fm
  /* 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);
@@@ -811,7 -814,6 +814,7 @@@ const char *show_date_relative(unsigne
                               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 *);
diff --combined merge-recursive.c
index 656e769b447f26247aa9672baf327a256b6bfeb1,4dcf417582bd4ea8c7bbe02252064c7cab5d7c48..638076ec6ecde537b51041d1bdf4ef5a00a4b3cc
@@@ -20,6 -20,7 +20,7 @@@
  #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)
@@@ -136,10 -137,16 +137,10 @@@ static void output_commit_title(struct 
                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);
                }
        }
  }
@@@ -179,7 -186,7 +180,7 @@@ static int git_merge_trees(int index_on
        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);
@@@ -519,13 -526,15 +520,15 @@@ static void update_file_flags(struct me
                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)
@@@ -710,8 -719,8 +713,8 @@@ static struct merge_file_info merge_fil
                        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);
  
@@@ -800,8 -809,7 +803,8 @@@ static int process_renames(struct merge
                           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++) {
@@@ -1173,48 -1181,26 +1176,48 @@@ static int process_entry(struct merge_o
        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;
        }
diff --combined path.c
index 6b23023095d7e1a5cfc1aef8db6d6e5fb56b32de,e32c61c2fdb7fa6dcfe66adabe4f51f849b3f0ff..a2c9d1e24afb9d1250c846791607c49d82e9565e
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -122,6 -122,44 +122,44 @@@ char *git_path(const char *fmt, ...
        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)
@@@ -316,8 -354,6 +354,8 @@@ char *expand_user_path(const char *path
                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);
diff --combined refs.c
index b5400674d7a1fcf4cc16dd630b8671b2ad7b9f7b,37e27946857d5faaf2a306ec610d84554c566494..e3c05110e58ca5684f9c43b1e1a5eb2587c96828
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -157,7 -157,7 +157,7 @@@ static struct cached_refs 
        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;
@@@ -229,23 -229,45 +229,45 @@@ void clear_extra_refs(void
        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);
@@@ -322,10 -356,16 +356,16 @@@ void warn_dangling_symref(FILE *fp, con
        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;
@@@ -459,7 -499,7 +499,7 @@@ const char *resolve_ref(const char *ref
                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);
@@@ -588,7 -628,7 +628,7 @@@ int peel_ref(const char *ref, unsigned 
                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)) {
@@@ -615,12 -655,12 +655,12 @@@ fallback
        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;
  
@@@ -657,24 -697,54 +697,54 @@@ end_each
        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,
@@@ -734,7 -819,7 +819,7 @@@ int for_each_glob_ref(each_ref_fn fn, c
  
  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);
  }
  
@@@ -958,7 -1043,7 +1043,7 @@@ static struct ref_lock *lock_ref_sha1_b
         * 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;
        }
@@@ -1021,7 -1106,7 +1106,7 @@@ static int repack_without_ref(const cha
        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;
@@@ -1090,15 -1175,6 +1175,15 @@@ int delete_ref(const char *refname, con
        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;
diff --combined submodule.c
index 7f0da48bc25e086aa8dd55b5a6f7f9e54e66d41a,dcb4b5da7166c865d06d55014d650a35b4abb998..91a47587478ae0550be8f41c00cb1749c85834f4
@@@ -6,10 -6,7 +6,11 @@@
  #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)
  {
@@@ -50,90 -47,16 +51,90 @@@ done
        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);
  }
  
@@@ -296,3 -219,163 +297,163 @@@ unsigned is_submodule_modified(const ch
        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;
+ }
diff --combined submodule.h
index 8ac4037b56210d54616dfa68655722b246c26f1b,b0a571c7f0b454ab0b5d7736aa561baec467af89..386f410a66d9c431a61c0e26c773f1b3452f0d09
@@@ -3,15 -3,13 +3,17 @@@
  
  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