{"thread":{"id":"24309","subject":"[PATCH v2 3/4] extent setup_revisions() so it works with submodules","startedAt":"2010-07-06T19:34:30Z","lastAt":"2010-07-08T00:34:46Z","messageCount":11,"participants":["Heiko Voigt","Johan Herland","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":4},"messages":[{"id":"144952","messageId":"cover.1278444110.git.hvoigt@hvoigt.net","threadId":"24309","inReplyTo":null,"subject":"[PATCH v2 0/4] implement automatic submodule merge strategy when possible","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-06T19:34:30Z","receivedAt":"2010-07-06T19:34:30Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"\nChanges since the previous iteration:\n\n * Add missing option --ancestry-path to setup_revisions()\n * Drop unnecessary caching of submodule refs\n * Use buf.len instead of NUL-padding for path buffer in\n   git_path_submodule()\n * Extend the testcases so they will check for the correct suggestion\n   output in case of a failed merge.\n * cleanup of some comments\n\n\nHeiko Voigt (4):\n  add missing && to submodule-merge testcase\n  teach ref iteration module about submodules\n  extent setup_revisions() so it works with submodules\n  implement automatic fast forward merge for submodules\n\n cache.h                    |    3 +\n merge-recursive.c          |    9 ++-\n path.c                     |   38 +++++++++++\n refs.c                     |  149 ++++++++++++++++++++++++++++++++---------\n refs.h                     |    8 ++\n revision.c                 |   32 +++++----\n revision.h                 |    1 +\n submodule.c                |  158 ++++++++++++++++++++++++++++++++++++++++++++\n submodule.h                |    2 +\n t/t7405-submodule-merge.sh |  129 ++++++++++++++++++++++++++++++++++--\n 10 files changed, 473 insertions(+), 56 deletions(-)\n\n-- \n1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"144953","messageId":"5eacc71cb957723a780d20567cb66a8b83384253.1278444110.git.hvoigt@hvoigt.net","threadId":"24309","inReplyTo":"cover.1278444110.git.hvoigt@hvoigt.net","subject":"[PATCH v2 1/4] add missing && to submodule-merge testcase","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-06T19:34:31Z","receivedAt":"2010-07-06T19:34:31Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n t/t7405-submodule-merge.sh |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh\nindex 9a21f78..4a7b893 100755\n--- a/t/t7405-submodule-merge.sh\n+++ b/t/t7405-submodule-merge.sh\n@@ -45,7 +45,7 @@ test_expect_success setup '\n \t git commit -m sub-b) &&\n \tgit add sub &&\n \ttest_tick &&\n-\tgit commit -m b\n+\tgit commit -m b &&\n \n \tgit checkout -b c a &&\n \tgit merge -s ours b &&\n-- \n1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"144954","messageId":"17527e05975d1a23b09059a9cb2ae2180d296165.1278444110.git.hvoigt@hvoigt.net","threadId":"24309","inReplyTo":"cover.1278444110.git.hvoigt@hvoigt.net","subject":"[PATCH v2 2/4] teach ref iteration module about submodules","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-06T19:34:32Z","receivedAt":"2010-07-06T19:34:32Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"We will use this in a later patch to extend setup_revisions() to\nload revisions directly from a submodule.\n\nSigned-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n cache.h |    3 ++\n path.c  |   38 ++++++++++++++++++++\n refs.c  |  118 ++++++++++++++++++++++++++++++++++++++++++++++-----------------\n 3 files changed, 127 insertions(+), 32 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex c9fa3df..32932e8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -641,6 +641,9 @@ extern char *git_pathdup(const char *fmt, ...)\n /* Return a statically allocated filename matching the sha1 signature */\n extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern char *git_path_submodule(const char *path, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+\n extern char *sha1_file_name(const unsigned char *sha1);\n extern char *sha1_pack_name(const unsigned char *sha1);\n extern char *sha1_pack_index_name(const unsigned char *sha1);\ndiff --git a/path.c b/path.c\nindex b4c8d91..e32c61c 100644\n--- a/path.c\n+++ b/path.c\n@@ -122,6 +122,44 @@ char *git_path(const char *fmt, ...)\n \treturn cleanup_path(pathname);\n }\n \n+char *git_path_submodule(const char *path, const char *fmt, ...)\n+{\n+\tchar *pathname = get_pathname();\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *git_dir;\n+\tva_list args;\n+\tunsigned len;\n+\n+\tlen = strlen(path);\n+\tif (len > PATH_MAX-100)\n+\t\treturn bad_path;\n+\n+\tstrbuf_addstr(&buf, path);\n+\tif (len && path[len-1] != '/')\n+\t\tstrbuf_addch(&buf, '/');\n+\tstrbuf_addstr(&buf, \".git\");\n+\n+\tgit_dir = read_gitfile_gently(buf.buf);\n+\tif (git_dir) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, git_dir);\n+\t}\n+\tstrbuf_addch(&buf, '/');\n+\n+\tif (buf.len >= PATH_MAX)\n+\t\treturn bad_path;\n+\tmemcpy(pathname, buf.buf, buf.len + 1);\n+\n+\tstrbuf_release(&buf);\n+\tlen = strlen(pathname);\n+\n+\tva_start(args, fmt);\n+\tlen += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);\n+\tva_end(args);\n+\tif (len >= PATH_MAX)\n+\t\treturn bad_path;\n+\treturn cleanup_path(pathname);\n+}\n \n /* git_mkstemp() - create tmp file honoring TMPDIR variable */\n int git_mkstemp(char *path, size_t len, const char *template)\ndiff --git a/refs.c b/refs.c\nindex 6f486ae..c8649b1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -157,7 +157,7 @@ static struct cached_refs {\n \tchar did_packed;\n \tstruct ref_list *loose;\n \tstruct ref_list *packed;\n-} cached_refs;\n+} cached_refs, submodule_refs;\n static struct ref_list *current_ref;\n \n static struct ref_list *extra_refs;\n@@ -229,23 +229,45 @@ void clear_extra_refs(void)\n \textra_refs = NULL;\n }\n \n-static struct ref_list *get_packed_refs(void)\n+static struct ref_list *get_packed_refs(const char *submodule)\n {\n-\tif (!cached_refs.did_packed) {\n-\t\tFILE *f = fopen(git_path(\"packed-refs\"), \"r\");\n-\t\tcached_refs.packed = NULL;\n+\tconst char *packed_refs_file;\n+\tstruct cached_refs *refs;\n+\n+\tif (submodule) {\n+\t\tpacked_refs_file = git_path_submodule(submodule, \"packed-refs\");\n+\t\trefs = &submodule_refs;\n+\t\tfree_ref_list(refs->packed);\n+\t} else {\n+\t\tpacked_refs_file = git_path(\"packed-refs\");\n+\t\trefs = &cached_refs;\n+\t}\n+\n+\tif (!refs->did_packed || submodule) {\n+\t\tFILE *f = fopen(packed_refs_file, \"r\");\n+\t\trefs->packed = NULL;\n \t\tif (f) {\n-\t\t\tread_packed_refs(f, &cached_refs);\n+\t\t\tread_packed_refs(f, refs);\n \t\t\tfclose(f);\n \t\t}\n-\t\tcached_refs.did_packed = 1;\n+\t\trefs->did_packed = 1;\n \t}\n-\treturn cached_refs.packed;\n+\treturn refs->packed;\n }\n \n-static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)\n+static struct ref_list *get_ref_dir(const char *submodule, const char *base,\n+\t\t\t\t    struct ref_list *list)\n {\n-\tDIR *dir = opendir(git_path(\"%s\", base));\n+\tDIR *dir;\n+\tconst char *path;\n+\n+\tif (submodule)\n+\t\tpath = git_path_submodule(submodule, \"%s\", base);\n+\telse\n+\t\tpath = git_path(\"%s\", base);\n+\n+\n+\tdir = opendir(path);\n \n \tif (dir) {\n \t\tstruct dirent *de;\n@@ -261,6 +283,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)\n \t\t\tstruct stat st;\n \t\t\tint flag;\n \t\t\tint namelen;\n+\t\t\tconst char *refdir;\n \n \t\t\tif (de->d_name[0] == '.')\n \t\t\t\tcontinue;\n@@ -270,16 +293,27 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)\n \t\t\tif (has_extension(de->d_name, \".lock\"))\n \t\t\t\tcontinue;\n \t\t\tmemcpy(ref + baselen, de->d_name, namelen+1);\n-\t\t\tif (stat(git_path(\"%s\", ref), &st) < 0)\n+\t\t\trefdir = submodule\n+\t\t\t\t? git_path_submodule(submodule, \"%s\", ref)\n+\t\t\t\t: git_path(\"%s\", ref);\n+\t\t\tif (stat(refdir, &st) < 0)\n \t\t\t\tcontinue;\n \t\t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\t\tlist = get_ref_dir(ref, list);\n+\t\t\t\tlist = get_ref_dir(submodule, ref, list);\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tif (!resolve_ref(ref, sha1, 1, &flag)) {\n+\t\t\tif (submodule) {\n \t\t\t\thashclr(sha1);\n-\t\t\t\tflag |= REF_BROKEN;\n-\t\t\t}\n+\t\t\t\tflag = 0;\n+\t\t\t\tif (resolve_gitlink_ref(submodule, ref, sha1) < 0) {\n+\t\t\t\t\thashclr(sha1);\n+\t\t\t\t\tflag |= REF_BROKEN;\n+\t\t\t\t}\n+\t\t\t} else\n+\t\t\t\tif (!resolve_ref(ref, sha1, 1, &flag)) {\n+\t\t\t\t\thashclr(sha1);\n+\t\t\t\t\tflag |= REF_BROKEN;\n+\t\t\t\t}\n \t\t\tlist = add_ref(ref, sha1, flag, list, NULL);\n \t\t}\n \t\tfree(ref);\n@@ -322,10 +356,16 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)\n \tfor_each_rawref(warn_if_dangling_symref, &data);\n }\n \n-static struct ref_list *get_loose_refs(void)\n+static struct ref_list *get_loose_refs(const char *submodule)\n {\n+\tif (submodule) {\n+\t\tfree_ref_list(submodule_refs.loose);\n+\t\tsubmodule_refs.loose = get_ref_dir(submodule, \"refs\", NULL);\n+\t\treturn submodule_refs.loose;\n+\t}\n+\n \tif (!cached_refs.did_loose) {\n-\t\tcached_refs.loose = get_ref_dir(\"refs\", NULL);\n+\t\tcached_refs.loose = get_ref_dir(NULL, \"refs\", NULL);\n \t\tcached_refs.did_loose = 1;\n \t}\n \treturn cached_refs.loose;\n@@ -459,7 +499,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *\n \t\tgit_snpath(path, sizeof(path), \"%s\", ref);\n \t\t/* Special case: non-existing file. */\n \t\tif (lstat(path, &st) < 0) {\n-\t\t\tstruct ref_list *list = get_packed_refs();\n+\t\t\tstruct ref_list *list = get_packed_refs(NULL);\n \t\t\twhile (list) {\n \t\t\t\tif (!strcmp(ref, list->name)) {\n \t\t\t\t\thashcpy(sha1, list->sha1);\n@@ -588,7 +628,7 @@ int peel_ref(const char *ref, unsigned char *sha1)\n \t\treturn -1;\n \n \tif ((flag & REF_ISPACKED)) {\n-\t\tstruct ref_list *list = get_packed_refs();\n+\t\tstruct ref_list *list = get_packed_refs(NULL);\n \n \t\twhile (list) {\n \t\t\tif (!strcmp(list->name, ref)) {\n@@ -615,12 +655,12 @@ fallback:\n \treturn -1;\n }\n \n-static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,\n-\t\t\t   int flags, void *cb_data)\n+static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,\n+\t\t\t   int trim, int flags, void *cb_data)\n {\n \tint retval = 0;\n-\tstruct ref_list *packed = get_packed_refs();\n-\tstruct ref_list *loose = get_loose_refs();\n+\tstruct ref_list *packed = get_packed_refs(submodule);\n+\tstruct ref_list *loose = get_loose_refs(submodule);\n \n \tstruct ref_list *extra;\n \n@@ -657,24 +697,38 @@ end_each:\n \treturn retval;\n }\n \n-int head_ref(each_ref_fn fn, void *cb_data)\n+\n+static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n \tunsigned char sha1[20];\n \tint flag;\n \n+\tif (submodule) {\n+\t\tif (resolve_gitlink_ref(submodule, \"HEAD\", sha1) == 0)\n+\t\t\treturn fn(\"HEAD\", sha1, 0, cb_data);\n+\n+\t\treturn 0;\n+\t}\n+\n \tif (resolve_ref(\"HEAD\", sha1, 1, &flag))\n \t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n+\n \treturn 0;\n }\n \n+int head_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn do_head_ref(NULL, fn, cb_data);\n+}\n+\n int for_each_ref(each_ref_fn fn, void *cb_data)\n {\n-\treturn do_for_each_ref(\"refs/\", fn, 0, 0, cb_data);\n+\treturn do_for_each_ref(NULL, \"refs/\", fn, 0, 0, cb_data);\n }\n \n int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n {\n-\treturn do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);\n+\treturn do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);\n }\n \n int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n@@ -694,7 +748,7 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n \n int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n {\n-\treturn do_for_each_ref(\"refs/replace/\", fn, 13, 0, cb_data);\n+\treturn do_for_each_ref(NULL, \"refs/replace/\", fn, 13, 0, cb_data);\n }\n \n int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n@@ -734,7 +788,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)\n \n int for_each_rawref(each_ref_fn fn, void *cb_data)\n {\n-\treturn do_for_each_ref(\"refs/\", fn, 0,\n+\treturn do_for_each_ref(NULL, \"refs/\", fn, 0,\n \t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n@@ -958,7 +1012,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char\n \t * name is a proper prefix of our refname.\n \t */\n \tif (missing &&\n-\t     !is_refname_available(ref, NULL, get_packed_refs(), 0)) {\n+\t     !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -1021,7 +1075,7 @@ static int repack_without_ref(const char *refname)\n \tint fd;\n \tint found = 0;\n \n-\tpacked_ref_list = get_packed_refs();\n+\tpacked_ref_list = get_packed_refs(NULL);\n \tfor (list = packed_ref_list; list; list = list->next) {\n \t\tif (!strcmp(refname, list->name)) {\n \t\t\tfound = 1;\n@@ -1110,10 +1164,10 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)\n \tif (!symref)\n \t\treturn error(\"refname %s not found\", oldref);\n \n-\tif (!is_refname_available(newref, oldref, get_packed_refs(), 0))\n+\tif (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))\n \t\treturn 1;\n \n-\tif (!is_refname_available(newref, oldref, get_loose_refs(), 0))\n+\tif (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))\n \t\treturn 1;\n \n \tlock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);\n-- \n1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"144950","messageId":"ab9c0f88b30060401d99735cb78eec7cc1e95b86.1278444110.git.hvoigt@hvoigt.net","threadId":"24309","inReplyTo":"cover.1278444110.git.hvoigt@hvoigt.net","subject":"[PATCH v2 3/4] extent setup_revisions() so it works with submodules","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-06T19:34:33Z","receivedAt":"2010-07-06T19:34:33Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n refs.c     |   31 +++++++++++++++++++++++++++++++\n refs.h     |    8 ++++++++\n revision.c |   32 ++++++++++++++++++--------------\n revision.h |    1 +\n 4 files changed, 58 insertions(+), 14 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c8649b1..37e2794 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -721,31 +721,62 @@ int head_ref(each_ref_fn fn, void *cb_data)\n \treturn do_head_ref(NULL, fn, cb_data);\n }\n \n+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn do_head_ref(submodule, fn, cb_data);\n+}\n+\n int for_each_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(NULL, \"refs/\", fn, 0, 0, cb_data);\n }\n \n+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn do_for_each_ref(submodule, \"refs/\", fn, 0, 0, cb_data);\n+}\n+\n int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);\n }\n \n+int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n+\t\teach_ref_fn fn, void *cb_data)\n+{\n+\treturn do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);\n+}\n+\n int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn for_each_ref_in(\"refs/tags/\", fn, cb_data);\n }\n \n+int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/tags/\", fn, cb_data);\n+}\n+\n int for_each_branch_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn for_each_ref_in(\"refs/heads/\", fn, cb_data);\n }\n \n+int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/heads/\", fn, cb_data);\n+}\n+\n int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn for_each_ref_in(\"refs/remotes/\", fn, cb_data);\n }\n \n+int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n+}\n+\n int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(NULL, \"refs/replace/\", fn, 13, 0, cb_data);\ndiff --git a/refs.h b/refs.h\nindex 762ce50..5e7a9a5 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -28,6 +28,14 @@ extern int for_each_replace_ref(each_ref_fn, void *);\n extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);\n extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);\n \n+extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n+\t\teach_ref_fn fn, void *cb_data);\n+extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n+\n static inline const char *has_glob_specials(const char *pattern)\n {\n \treturn strpbrk(pattern, \"?*[\");\ndiff --git a/revision.c b/revision.c\nindex 7e82efd..5f2cf1e 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -820,12 +820,12 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,\n \tcb->all_flags = flags;\n }\n \n-static void handle_refs(struct rev_info *revs, unsigned flags,\n-\t\tint (*for_each)(each_ref_fn, void *))\n+static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,\n+\t\tint (*for_each)(const char *, each_ref_fn, void *))\n {\n \tstruct all_refs_cb cb;\n \tinit_all_refs_cb(&cb, revs, flags);\n-\tfor_each(handle_one_ref, &cb);\n+\tfor_each(submodule, handle_one_ref, &cb);\n }\n \n static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)\n@@ -1417,14 +1417,14 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,\n \tctx->argc -= n;\n }\n \n-static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data)\n+static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n-\treturn for_each_ref_in(\"refs/bisect/bad\", fn, cb_data);\n+\treturn for_each_ref_in_submodule(submodule, \"refs/bisect/bad\", fn, cb_data);\n }\n \n-static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)\n+static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n-\treturn for_each_ref_in(\"refs/bisect/good\", fn, cb_data);\n+\treturn for_each_ref_in_submodule(submodule, \"refs/bisect/good\", fn, cb_data);\n }\n \n static void append_prune_data(const char ***prune_data, const char **av)\n@@ -1466,6 +1466,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n {\n \tint i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;\n \tconst char **prune_data = NULL;\n+\tconst char *submodule = NULL;\n+\n+\tif (opt)\n+\t\tsubmodule = opt->submodule;\n \n \t/* First, search for \"--\" */\n \tseen_dashdash = 0;\n@@ -1490,26 +1494,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\t\tint opts;\n \n \t\t\tif (!strcmp(arg, \"--all\")) {\n-\t\t\t\thandle_refs(revs, flags, for_each_ref);\n-\t\t\t\thandle_refs(revs, flags, head_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags, for_each_ref_submodule);\n+\t\t\t\thandle_refs(submodule, revs, flags, head_ref_submodule);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--branches\")) {\n-\t\t\t\thandle_refs(revs, flags, for_each_branch_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags, for_each_branch_ref_submodule);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--bisect\")) {\n-\t\t\t\thandle_refs(revs, flags, for_each_bad_bisect_ref);\n-\t\t\t\thandle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags, for_each_bad_bisect_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);\n \t\t\t\trevs->bisect = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--tags\")) {\n-\t\t\t\thandle_refs(revs, flags, for_each_tag_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags, for_each_tag_ref_submodule);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--remotes\")) {\n-\t\t\t\thandle_refs(revs, flags, for_each_remote_ref);\n+\t\t\t\thandle_refs(submodule, revs, flags, for_each_remote_ref_submodule);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!prefixcmp(arg, \"--glob=\")) {\ndiff --git a/revision.h b/revision.h\nindex 36fdf22..05659c6 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -151,6 +151,7 @@ extern volatile show_early_output_fn_t show_early_output;\n struct setup_revision_opt {\n \tconst char *def;\n \tvoid (*tweak)(struct rev_info *, struct setup_revision_opt *);\n+\tconst char *submodule;\n };\n \n extern void init_revisions(struct rev_info *revs, const char *prefix);\n-- \n1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"144951","messageId":"f17d78656a71558fd290e0b84cad03f22f6fcbd8.1278444110.git.hvoigt@hvoigt.net","threadId":"24309","inReplyTo":"cover.1278444110.git.hvoigt@hvoigt.net","subject":"[PATCH v2 4/4] implement automatic fast forward merge for submodules","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-06T19:34:34Z","receivedAt":"2010-07-06T19:34:34Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"This implements a simple merge strategy for submodule hashes. We check\nwhether one side of the merge candidates is already contained in the\nother and then merge automatically.\n\nIf both sides contain changes we search for a merge in the submodule.\nIn case a single one exists we check that out and suggest it as the\nmerge resolution. A list of candidates is returned when we find multiple\nmerges that contain both sides of the changes.\n\nThis is useful for a workflow in which the developers can publish topic\nbranches in submodules and a seperate maintainer merges them. In case\nthe developers always wait until their branch gets merged before tracking\nthem in the superproject all merges of branches that contain submodule\nchanges will be resolved automatically. If developers choose to track\ntheir feature branch the maintainer might get a conflict but git will\nsearch the submodule for a merge and suggest it/them as a resolution.\n\nSigned-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n merge-recursive.c          |    9 ++-\n submodule.c                |  158 ++++++++++++++++++++++++++++++++++++++++++++\n submodule.h                |    2 +\n t/t7405-submodule-merge.sh |  127 +++++++++++++++++++++++++++++++++--\n 4 files changed, 287 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 856e98c..4dcf417 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -20,6 +20,7 @@\n #include \"attr.h\"\n #include \"merge-recursive.h\"\n #include \"dir.h\"\n+#include \"submodule.h\"\n \n static struct tree *shift_tree_object(struct tree *one, struct tree *two,\n \t\t\t\t      const char *subtree_shift)\n@@ -525,13 +526,15 @@ static void update_file_flags(struct merge_options *o,\n \t\tvoid *buf;\n \t\tunsigned long size;\n \n-\t\tif (S_ISGITLINK(mode))\n+\t\tif (S_ISGITLINK(mode)) {\n \t\t\t/*\n \t\t\t * We may later decide to recursively descend into\n \t\t\t * the submodule directory and update its index\n \t\t\t * and/or work tree, but we do not do that now.\n \t\t\t */\n+\t\t\tupdate_wd = 0;\n \t\t\tgoto update_index;\n+\t\t}\n \n \t\tbuf = read_sha1_file(sha, &type, &size);\n \t\tif (!buf)\n@@ -716,8 +719,8 @@ static struct merge_file_info merge_file(struct merge_options *o,\n \t\t\tfree(result_buf.ptr);\n \t\t\tresult.clean = (merge_status == 0);\n \t\t} else if (S_ISGITLINK(a->mode)) {\n-\t\t\tresult.clean = 0;\n-\t\t\thashcpy(result.sha, a->sha1);\n+\t\t\tresult.clean = merge_submodule(result.sha, one->path, one->sha1,\n+\t\t\t\t\t\t       a->sha1, b->sha1);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\thashcpy(result.sha, a->sha1);\n \ndiff --git a/submodule.c b/submodule.c\nindex 61cb6e2..0f4e6ab 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -6,6 +6,7 @@\n #include \"revision.h\"\n #include \"run-command.h\"\n #include \"diffcore.h\"\n+#include \"refs.h\"\n \n static int add_submodule_odb(const char *path)\n {\n@@ -218,3 +219,160 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)\n \tstrbuf_release(&buf);\n \treturn dirty_submodule;\n }\n+\n+static int find_first_merges(struct object_array *result, const char *path,\n+\t\tstruct commit *a, struct commit *b)\n+{\n+\tint i, j;\n+\tstruct object_array merges;\n+\tstruct commit *commit;\n+\tint contains_another;\n+\n+\tchar merged_revision[42];\n+\tconst char *rev_args[] = { \"rev-list\", \"--merges\", \"--ancestry-path\",\n+\t\t\t\t   \"--all\", merged_revision, NULL };\n+\tstruct rev_info revs;\n+\tstruct setup_revision_opt rev_opts;\n+\n+\tmemset(&merges, 0, sizeof(merges));\n+\tmemset(result, 0, sizeof(struct object_array));\n+\tmemset(&rev_opts, 0, sizeof(rev_opts));\n+\n+\t/* get all revisions that merge commit a */\n+\tsnprintf(merged_revision, sizeof(merged_revision), \"^%s\",\n+\t\t\tfind_unique_abbrev(a->object.sha1, 40));\n+\tinit_revisions(&revs, NULL);\n+\trev_opts.submodule = path;\n+\tsetup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);\n+\n+\t/* save all revisions from the above list that contain b */\n+\tif (prepare_revision_walk(&revs))\n+\t\tdie(\"revision walk setup failed\");\n+\twhile ((commit = get_revision(&revs)) != NULL) {\n+\t\tstruct object *o = &(commit->object);\n+\t\tif (in_merge_bases(b, (struct commit **) &o, 1)) {\n+\t\t\tadd_object_array(o, NULL, &merges);\n+\t\t}\n+\t}\n+\n+\t/* Now we've got all merges that contain a and b. Prune all\n+\t * merges that contain another found merge and save them in\n+\t * result.\n+\t */\n+\tfor (i = 0; i < merges.nr; i++) {\n+\t\tstruct commit *m1 = (struct commit *) merges.objects[i].item;\n+\n+\t\tcontains_another = 0;\n+\t\tfor (j = 0; j < merges.nr; j++) {\n+\t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n+\t\t\tif (i != j && in_merge_bases(m2, &m1, 1)) {\n+\t\t\t\tcontains_another = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!contains_another)\n+\t\t\tadd_object_array(merges.objects[i].item,\n+\t\t\t\t\t merges.objects[i].name, result);\n+\t}\n+\n+\tfree(merges.objects);\n+\treturn result->nr;\n+}\n+\n+static void print_commit(struct commit *commit)\n+{\n+\tstatic const char *format = \" %h: %m %s\";\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tctx.date_mode = DATE_NORMAL;\n+\tformat_commit_message(commit, format, &sb, &ctx);\n+\tstrbuf_addstr(&sb, \"\\n\");\n+\tfprintf(stderr, \"%s\", sb.buf);\n+}\n+\n+int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n+\t\t    const unsigned char a[20], const unsigned char b[20])\n+{\n+\tstruct commit *commit_base, *commit_a, *commit_b;\n+\tint parent_count;\n+\tstruct object_array merges;\n+\n+\tint i;\n+\n+\t/* store a in result in case we fail */\n+\thashcpy(result, a);\n+\n+\t/* we can not handle deletion conflicts */\n+\tif (is_null_sha1(base))\n+\t\treturn 0;\n+\tif (is_null_sha1(a))\n+\t\treturn 0;\n+\tif (is_null_sha1(b))\n+\t\treturn 0;\n+\n+\tif (add_submodule_odb(path)) {\n+\t\twarning(\"Failed to merge submodule %s (not checked out)\", path);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!(commit_base = lookup_commit_reference(base)) ||\n+\t    !(commit_a = lookup_commit_reference(a)) ||\n+\t    !(commit_b = lookup_commit_reference(b)))\n+\t{\n+\t\twarning(\"Failed to merge submodule %s (commits not present)\", path);\n+\t\treturn 0;\n+\t}\n+\n+\t/* check whether both changes are forward */\n+\tif (!in_merge_bases(commit_base, &commit_a, 1) ||\n+\t    !in_merge_bases(commit_base, &commit_b, 1))\n+\t{\n+\t\twarning(\"Submodule rewound can not merge\");\n+\t\treturn 0;\n+\t}\n+\n+\t/* 1. case a is contained in b or vice versa */\n+\tif (in_merge_bases(commit_a, &commit_b, 1)) {\n+\t\thashcpy(result, b);\n+\t\treturn 1;\n+\t}\n+\tif (in_merge_bases(commit_b, &commit_a, 1)) {\n+\t\thashcpy(result, a);\n+\t\treturn 1;\n+\t}\n+\n+\t/* 2. case there are one ore more merges that contain a and b in\n+\t * the submodule. If there is a single one present it as\n+\t * suggestion to the user but leave it marked unmerged so the\n+\t * user needs to confirm the resolution.\n+\t */\n+\n+\t/* find commit which merges them */\n+\tparent_count = find_first_merges(&merges, path, commit_a, commit_b);\n+\tif (!parent_count) {\n+\t\twarning(\"Failed to merge submodule %s (merge not found)\", path);\n+\t\tgoto finish;\n+\t}\n+\n+\tif (parent_count != 1) {\n+\t\twarning(\"Failed to merge submodule %s (multiple merges found):\", path);\n+\t\tfor (i = 0; i < merges.nr; i++) {\n+\t\t\tprint_commit((struct commit *) merges.objects[i].item);\n+\t\t}\n+\t\tgoto finish;\n+\t}\n+\n+\twarning(\"Failed to merge submodule %s (not fast-forward):\\n\", path);\n+\tfprintf(stderr, \"Found a possible merge resolution for the submodule:\\n\");\n+\tprint_commit((struct commit *) merges.objects[0].item);\n+\tfprintf(stderr, \"If this is correct simply add it to the index for example\\n\"\n+\t\t\t\"by using:\\n\\n\"\n+\t\t\t\"  git update-index --cacheinfo 160000 %s \\\"%s\\\"\\n\\n\"\n+\t\t\t\"which will accept this suggestion.\\n\",\n+\t\tsha1_to_hex(merges.objects[0].item->sha1), path);\n+\n+finish:\n+\tfree(merges.objects);\n+\treturn 0;\n+}\ndiff --git a/submodule.h b/submodule.h\nindex 6fd3bb4..b0a571c 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -9,5 +9,7 @@ void show_submodule_summary(FILE *f, const char *path,\n \t\tunsigned dirty_submodule,\n \t\tconst char *del, const char *add, const char *reset);\n unsigned is_submodule_modified(const char *path, int ignore_untracked);\n+int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n+\t\t    const unsigned char a[20], const unsigned char b[20]);\n \n #endif\ndiff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh\nindex 4a7b893..0f568ab 100755\n--- a/t/t7405-submodule-merge.sh\n+++ b/t/t7405-submodule-merge.sh\n@@ -54,13 +54,128 @@ test_expect_success setup '\n \tgit merge -s ours a\n '\n \n-test_expect_success 'merging with modify/modify conflict' '\n+# History setup\n+#\n+#      b\n+#    /   \\\n+#   a     d\n+#    \\   /\n+#      c\n+#\n+# a in the main repository records to sub-a in the submodule and\n+# analogous b and c. d should be automatically found by merging c into\n+# b in the main repository.\n+test_expect_success 'setup for merge search' '\n+\tmkdir merge-search &&\n+\tcd merge-search &&\n+\tgit init &&\n+\tmkdir sub &&\n+\t(cd sub &&\n+\t git init &&\n+\t echo \"file-a\" > file-a &&\n+\t git add file-a &&\n+\t git commit -m \"sub-a\" &&\n+\t git checkout -b sub-a) &&\n+\tgit add sub &&\n+\tgit commit -m \"a\" &&\n+\tgit checkout -b a &&\n+\n+\tgit checkout -b b &&\n+\t(cd sub &&\n+\t git checkout -b sub-b &&\n+\t echo \"file-b\" > file-b &&\n+\t git add file-b &&\n+\t git commit -m \"sub-b\") &&\n+\tgit commit -a -m \"b\" &&\n+\n+\tgit checkout -b c a &&\n+\t(cd sub &&\n+\t git checkout -b sub-c sub-a &&\n+\t echo \"file-c\" > file-c &&\n+\t git add file-c &&\n+\t git commit -m \"sub-c\") &&\n+\tgit commit -a -m \"c\" &&\n+\n+\tgit checkout -b d a &&\n+\t(cd sub &&\n+\t git checkout -b sub-d sub-b &&\n+\t git merge sub-c) &&\n+\tgit commit -a -m \"d\" &&\n+\tgit checkout -b test b &&\n+\tcd ..\n+'\n+\n+test_expect_success 'merge with one side as a fast-forward of the other' '\n+\t(cd merge-search &&\n+\t git checkout -b test-forward b &&\n+\t git merge d &&\n+\t git ls-tree test-forward | grep sub | cut -f1 | cut -f3 -d\" \" > actual &&\n+\t (cd sub &&\n+\t  git rev-parse sub-d > ../expect) &&\n+\t test_cmp actual expect)\n+'\n+\n+test_expect_success 'merging should conflict for non fast-forward' '\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward b &&\n+\t (cd sub &&\n+\t  git rev-parse sub-d > ../expect) &&\n+\t test_must_fail git merge c 2> actual  &&\n+\t grep $(<expect) actual > /dev/null &&\n+\t git reset --hard)\n+'\n+\n+test_expect_success 'merging should fail for ambigous common parent' '\n+\tcd merge-search &&\n+\tgit checkout -b test-ambigous b &&\n+\t(cd sub &&\n+\t git checkout -b ambigous sub-b &&\n+\t git merge sub-c &&\n+\t git rev-parse sub-d > ../expect1 &&\n+\t git rev-parse ambigous > ../expect2) &&\n+\ttest_must_fail git merge c 2> actual &&\n+\tgrep $(<expect1) actual > /dev/null &&\n+\tgrep $(<expect2) actual > /dev/null &&\n+\tgit reset --hard &&\n+\tcd ..\n+'\n+\n+# in a situation like this\n+#\n+# submodule tree:\n+#\n+#    sub-a --- sub-b --- sub-d\n+#\n+# main tree:\n+#\n+#    e (sub-a)\n+#   /\n+#  bb (sub-b)\n+#   \\\n+#    f (sub-d)\n+#\n+# A merge should fail because one change points backwards.\n+\n+test_expect_success 'merging should fail for changes that are backwards' '\n+\tcd merge-search &&\n+\tgit checkout -b bb a &&\n+\t(cd sub &&\n+\t git checkout sub-b) &&\n+\tgit commit -a -m \"bb\" &&\n+\n+\tgit checkout -b e bb &&\n+\t(cd sub &&\n+\t git checkout sub-a) &&\n+\tgit commit -a -m \"e\" &&\n+\n+\tgit checkout -b f bb &&\n+\t(cd sub &&\n+\t git checkout sub-d) &&\n+\tgit commit -a -m \"f\" &&\n \n-\tgit checkout -b test1 a &&\n-\ttest_must_fail git merge b &&\n-\ttest -f .git/MERGE_MSG &&\n-\tgit diff &&\n-\ttest -n \"$(git ls-files -u)\"\n+\tgit checkout -b test-backward e &&\n+\ttest_must_fail git merge f &&\n+\tcd ..\n '\n \n test_expect_success 'merging with a modify/modify conflict between merge bases' '\n-- \n1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"144971","messageId":"201007070152.17802.johan@herland.net","threadId":"24309","inReplyTo":"f17d78656a71558fd290e0b84cad03f22f6fcbd8.1278444110.git.hvoigt@hvoigt.net","subject":"Re: [PATCH v2 4/4] implement automatic fast forward merge for submodules","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-07-06T23:52:16Z","receivedAt":"2010-07-06T23:52:16Z","isPatch":true,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Tuesday 06 July 2010, Heiko Voigt wrote:\n> This implements a simple merge strategy for submodule hashes. We check\n> whether one side of the merge candidates is already contained in the\n> other and then merge automatically.\n> \n> If both sides contain changes we search for a merge in the submodule.\n> In case a single one exists we check that out and suggest it as the\n> merge resolution. A list of candidates is returned when we find multiple\n> merges that contain both sides of the changes.\n> \n> This is useful for a workflow in which the developers can publish topic\n> branches in submodules and a seperate maintainer merges them. In case\n> the developers always wait until their branch gets merged before tracking\n> them in the superproject all merges of branches that contain submodule\n> changes will be resolved automatically. If developers choose to track\n> their feature branch the maintainer might get a conflict but git will\n> search the submodule for a merge and suggest it/them as a resolution.\n> \n> Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n\nIn the commit subject: s/fast forward/fast-forward/\n\n> diff --git a/submodule.c b/submodule.c\n\n> +static int find_first_merges(struct object_array *result, const char *path,\n> +\t\tstruct commit *a, struct commit *b)\n\n[...]\n\n> +\t/* get all revisions that merge commit a */\n> +\tsnprintf(merged_revision, sizeof(merged_revision), \"^%s\",\n> +\t\t\tfind_unique_abbrev(a->object.sha1, 40));\n\nWhy do you call find_unique_abbrev(..., 40) here?\nIsn't sha1_to_hex(a->object.sha1) a better solution?\n\n> +\tinit_revisions(&revs, NULL);\n> +\trev_opts.submodule = path;\n> +\tsetup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);\n> +\n> +\t/* save all revisions from the above list that contain b */\n> +\tif (prepare_revision_walk(&revs))\n> +\t\tdie(\"revision walk setup failed\");\n> +\twhile ((commit = get_revision(&revs)) != NULL) {\n> +\t\tstruct object *o = &(commit->object);\n> +\t\tif (in_merge_bases(b, (struct commit **) &o, 1)) {\n\nWhy not s/(struct commit **) &o/&commit/ ?\n\n\n> +static void print_commit(struct commit *commit)\n> +{\n> +\tstatic const char *format = \" %h: %m %s\";\n\nYou don't need this 'format' variable; instead use the literal string below.\n\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tstruct pretty_print_context ctx = {0};\n> +\tctx.date_mode = DATE_NORMAL;\n> +\tformat_commit_message(commit, format, &sb, &ctx);\n> +\tstrbuf_addstr(&sb, \"\\n\");\n> +\tfprintf(stderr, \"%s\", sb.buf);\n\nDrop the strbuf_addstr() and add the newline directly in the fprintf()\nformat:\n\nfprintf(stderr, \"%s\\n\", sb.buf);\n\n\n> +int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n> +\t\t    const unsigned char a[20], const unsigned char b[20])\n\nWatch your line wrapping. Max 80 chars.\n\n> +\t\twarning(\"Submodule rewound can not merge\");\n\ns/Submodule rewound can not merge/\n  Failed to merge submodule %s (commits don't follow merge-base)/\n\t\n> +\t/* 1. case a is contained in b or vice versa */\n\ns/1. case/Case #1:/\n\n> +\t/* 2. case there are one ore more merges that contain a and b in\n> +\t * the submodule. If there is a single one present it as\n> +\t * suggestion to the user but leave it marked unmerged so the\n> +\t * user needs to confirm the resolution.\n> +\t */\n\ns/2. case/Case #2:/\n\ns/one ore more/one or more/\n\ns/If there is a single one present it as suggestion to the user but/\n  If there is only one, then present it a suggestion to the user, but/\n\n\n> +\t/* find commit which merges them */\n> +\tparent_count = find_first_merges(&merges, path, commit_a, commit_b);\n> +\tif (!parent_count) {\n> +\t\twarning(\"Failed to merge submodule %s (merge not found)\", path);\n\ns/merge not found/merge following commits not found/\n\nBTW, what about a\n\n#define MERGE_WARNING(path, msg) \\\n\twarning(\"Failed to merge submodule %s (%s)\", path, msg);\n\ntowards the top of merge_submodule, so you don't have to repeat\nthe same string over and over?\n\n> +\t\tgoto finish;\n> +\t}\n> +\n> +\tif (parent_count != 1) {\n> +\t\twarning(\"Failed to merge submodule %s (multiple merges found):\", path);\n> +\t\tfor (i = 0; i < merges.nr; i++) {\n> +\t\t\tprint_commit((struct commit *) merges.objects[i].item);\n> +\t\t}\n> +\t\tgoto finish;\n> +\t}\n> +\n> +\twarning(\"Failed to merge submodule %s (not fast-forward):\\n\", path);\n> +\tfprintf(stderr, \"Found a possible merge resolution for the submodule:\\n\");\n> +\tprint_commit((struct commit *) merges.objects[0].item);\n> +\tfprintf(stderr, \"If this is correct simply add it to the index for example\\n\"\n> +\t\t\t\"by using:\\n\\n\"\n> +\t\t\t\"  git update-index --cacheinfo 160000 %s \\\"%s\\\"\\n\\n\"\n> +\t\t\t\"which will accept this suggestion.\\n\",\n> +\t\tsha1_to_hex(merges.objects[0].item->sha1), path);\n> +\n> +finish:\n> +\tfree(merges.objects);\n> +\treturn 0;\n> +}\n\nHmm, you can get rid of the 'goto finish' by restructuring the code like this:\n\nparent_count = find_first_merges(&merges, path, commit_a, commit_b);\nswitch (parent_count) {\ncase 0:\n\tMERGE_WARNING(path, \"merge following commits not found\");\n\tbreak;\ncase 1:\n\tMERGE_WARNING(path, \"not fast-forward\");\n\tfprintf(...\n\t...\n\tbreak;\ndefault:\n\tMERGE_WARNING(path, \"multiple merges found\");\n\tfor (i = 0; i < merges.nr; i++)\n\t\tprint_commit((struct commit *) merges.objects[i].item);\n}\n\nfree(merges.objects);\nreturn 0;\n\n\n> diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh\n> index 4a7b893..0f568ab 100755\n> --- a/t/t7405-submodule-merge.sh\n> +++ b/t/t7405-submodule-merge.sh\n> @@ -54,13 +54,128 @@ test_expect_success setup '\n>  \tgit merge -s ours a\n>  '\n> \n> -test_expect_success 'merging with modify/modify conflict' '\n> +# History setup\n> +#\n> +#      b\n> +#    /   \\\n> +#   a     d\n> +#    \\   /\n> +#      c\n> +#\n> +# a in the main repository records to sub-a in the submodule and\n> +# analogous b and c. d should be automatically found by merging c into\n> +# b in the main repository.\n> +test_expect_success 'setup for merge search' '\n> +\tmkdir merge-search &&\n> +\tcd merge-search &&\n> +\tgit init &&\n> +\tmkdir sub &&\n> +\t(cd sub &&\n> +\t git init &&\n> +\t echo \"file-a\" > file-a &&\n> +\t git add file-a &&\n> +\t git commit -m \"sub-a\" &&\n> +\t git checkout -b sub-a) &&\n\nIf you just need to create a new branch, but not necessarily check it out,\nthen use 'git branch <name>' instead of 'git checkout -b <name>'.\n\n> +\tgit add sub &&\n> +\tgit commit -m \"a\" &&\n> +\tgit checkout -b a &&\n\nSame as above\n\n> +\tgit checkout -b b &&\n> +\t(cd sub &&\n> +\t git checkout -b sub-b &&\n> +\t echo \"file-b\" > file-b &&\n> +\t git add file-b &&\n> +\t git commit -m \"sub-b\") &&\n> +\tgit commit -a -m \"b\" &&\n> +\n> +\tgit checkout -b c a &&\n> +\t(cd sub &&\n> +\t git checkout -b sub-c sub-a &&\n> +\t echo \"file-c\" > file-c &&\n> +\t git add file-c &&\n> +\t git commit -m \"sub-c\") &&\n> +\tgit commit -a -m \"c\" &&\n> +\n> +\tgit checkout -b d a &&\n> +\t(cd sub &&\n> +\t git checkout -b sub-d sub-b &&\n> +\t git merge sub-c) &&\n> +\tgit commit -a -m \"d\" &&\n> +\tgit checkout -b test b &&\n\nsame as above\n\n> +\tcd ..\n> +'\n> +\n> +test_expect_success 'merge with one side as a fast-forward of the other' '\n> +\t(cd merge-search &&\n> +\t git checkout -b test-forward b &&\n> +\t git merge d &&\n> +\t git ls-tree test-forward | grep sub | cut -f1 | cut -f3 -d\" \" > actual &&\n\nUse \"git ls-tree test-forward sub\" instead of \"git ls-tree test-forward | grep sub\"\n\n> +\t (cd sub &&\n> +\t  git rev-parse sub-d > ../expect) &&\n> +\t test_cmp actual expect)\n> +'\n> +\n> +test_expect_success 'merging should conflict for non fast-forward' '\n> +\t(cd merge-search &&\n> +\t git checkout -b test-nonforward b &&\n> +\t (cd sub &&\n> +\t  git rev-parse sub-d > ../expect) &&\n> +\t test_must_fail \tgit merge c 2> actual  &&\n> +\t grep $(<expect) actual > /dev/null &&\n\nI cannot find the \"$(<expect)\" construct anywhere else in out test scripts.\nIs it portable? Should we maybe use \"$(cat expect)\" instead?\n\n> +\t git reset --hard)\n> +'\n> +\n> +test_expect_success 'merging should fail for ambigous common parent' '\n\ns/ambigous/ambiguous/\n\n> +\tcd merge-search &&\n> +\tgit checkout -b test-ambigous b &&\n> +\t(cd sub &&\n> +\t git checkout -b ambigous sub-b &&\n> +\t git merge sub-c &&\n> +\t git rev-parse sub-d > ../expect1 &&\n> +\t git rev-parse ambigous > ../expect2) &&\n\ns/ambigous/ambiguous/\n\n> +\ttest_must_fail git merge c 2> actual &&\n> +\tgrep $(<expect1) actual > /dev/null &&\n> +\tgrep $(<expect2) actual > /dev/null &&\n\nSame $(<foo) construct...\n\n> +\tgit reset --hard &&\n> +\tcd ..\n> +'\n> +\n> +# in a situation like this\n> +#\n> +# submodule tree:\n> +#\n> +#    sub-a --- sub-b --- sub-d\n> +#\n> +# main tree:\n> +#\n> +#    e (sub-a)\n> +#   /\n> +#  bb (sub-b)\n> +#   \\\n> +#    f (sub-d)\n> +#\n> +# A merge should fail because one change points backwards.\n\nInstead: A merge between e and f should fail because one of the submodule\ncommits (sub-a) don't descend from the submodule merge-base (sub-b).\n\n\nOtherwise, it looks good to me. Thanks for the effort!\n\n\nHave fun! :)\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"144972","messageId":"201007070153.05684.johan@herland.net","threadId":"24309","inReplyTo":"cover.1278444110.git.hvoigt@hvoigt.net","subject":"Re: [PATCH v2 0/4] implement automatic submodule merge strategy when possible","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-07-06T23:53:04Z","receivedAt":"2010-07-06T23:53:04Z","isPatch":true,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Tuesday 06 July 2010, Heiko Voigt wrote:\n> Changes since the previous iteration:\n> \n>  * Add missing option --ancestry-path to setup_revisions()\n>  * Drop unnecessary caching of submodule refs\n>  * Use buf.len instead of NUL-padding for path buffer in\n>    git_path_submodule()\n>  * Extend the testcases so they will check for the correct suggestion\n>    output in case of a failed merge.\n>  * cleanup of some comments\n> \n> \n> Heiko Voigt (4):\n>   add missing && to submodule-merge testcase\n>   teach ref iteration module about submodules\n>   extent setup_revisions() so it works with submodules\n>   implement automatic fast forward merge for submodules\n\nI've quickly looked through the patches, and the general logic seems good. \nThere are a few typos and other small issues to fix, though:\n\nPatch #3:\n\n- commit message: s/extent/Extend/ and s/so it works/to work/\n\nPatch #4:\n\n(see separate email)\n\n\nThanks for the effort.\n\n\nHave fun! :)\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"144999","messageId":"7veiff274b.fsf@alter.siamese.dyndns.org","threadId":"24309","inReplyTo":"ab9c0f88b30060401d99735cb78eec7cc1e95b86.1278444110.git.hvoigt@hvoigt.net","subject":"Re: [PATCH v2 3/4] extent setup_revisions() so it works with submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-07T05:28:20Z","receivedAt":"2010-07-07T05:28:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heiko Voigt <hvoigt@hvoigt.net> writes:\n\n> Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n\nThis needs a bit more explanation.  \"Exten_d_ setup_revisions()\" is fine,\nbut \"works with submodules\"???  How? and what do the caller need to do?\n\nSomething like...\n\n    Subject: setup_revisions(): allow walking history in a submodule\n\n    By passing the path to a submodule in opt->submodule, the function can\n    be used to walk history in the named submodule repository, instead of\n    the toplevel repository.\n\n\n\n> ---\n>  refs.c     |   31 +++++++++++++++++++++++++++++++\n>  refs.h     |    8 ++++++++\n>  revision.c |   32 ++++++++++++++++++--------------\n>  revision.h |    1 +\n>  4 files changed, 58 insertions(+), 14 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index c8649b1..37e2794 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -721,31 +721,62 @@ int head_ref(each_ref_fn fn, void *cb_data)\n>  \treturn do_head_ref(NULL, fn, cb_data);\n>  }\n>  \n> +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n> +{\n> +\treturn do_head_ref(submodule, fn, cb_data);\n> +}\n> +\n>  int for_each_ref(each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn do_for_each_ref(NULL, \"refs/\", fn, 0, 0, cb_data);\n>  }\n>  \n> +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n> +{\n> +\treturn do_for_each_ref(submodule, \"refs/\", fn, 0, 0, cb_data);\n> +}\n> +\n>  int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);\n>  }\n>  \n> +int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n> +\t\teach_ref_fn fn, void *cb_data)\n> +{\n> +\treturn do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);\n> +}\n> +\n>  int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn for_each_ref_in(\"refs/tags/\", fn, cb_data);\n>  }\n>  \n> +int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n> +{\n> +\treturn for_each_ref_in_submodule(submodule, \"refs/tags/\", fn, cb_data);\n> +}\n> +\n>  int for_each_branch_ref(each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn for_each_ref_in(\"refs/heads/\", fn, cb_data);\n>  }\n>  \n> +int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n> +{\n> +\treturn for_each_ref_in_submodule(submodule, \"refs/heads/\", fn, cb_data);\n> +}\n> +\n>  int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn for_each_ref_in(\"refs/remotes/\", fn, cb_data);\n>  }\n>  \n> +int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n> +{\n> +\treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n> +}\n> +\n>  int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n>  {\n>  \treturn do_for_each_ref(NULL, \"refs/replace/\", fn, 13, 0, cb_data);\n> diff --git a/refs.h b/refs.h\n> index 762ce50..5e7a9a5 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -28,6 +28,14 @@ extern int for_each_replace_ref(each_ref_fn, void *);\n>  extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);\n>  extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);\n>  \n> +extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n> +extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n> +extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n> +\t\teach_ref_fn fn, void *cb_data);\n> +extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n> +extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n> +extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n> +\n>  static inline const char *has_glob_specials(const char *pattern)\n>  {\n>  \treturn strpbrk(pattern, \"?*[\");\n> diff --git a/revision.c b/revision.c\n> index 7e82efd..5f2cf1e 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -820,12 +820,12 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,\n>  \tcb->all_flags = flags;\n>  }\n>  \n> -static void handle_refs(struct rev_info *revs, unsigned flags,\n> -\t\tint (*for_each)(each_ref_fn, void *))\n> +static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,\n> +\t\tint (*for_each)(const char *, each_ref_fn, void *))\n>  {\n>  \tstruct all_refs_cb cb;\n>  \tinit_all_refs_cb(&cb, revs, flags);\n> -\tfor_each(handle_one_ref, &cb);\n> +\tfor_each(submodule, handle_one_ref, &cb);\n>  }\n>  \n>  static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)\n> @@ -1417,14 +1417,14 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,\n>  \tctx->argc -= n;\n>  }\n>  \n> -static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data)\n> +static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n>  {\n> -\treturn for_each_ref_in(\"refs/bisect/bad\", fn, cb_data);\n> +\treturn for_each_ref_in_submodule(submodule, \"refs/bisect/bad\", fn, cb_data);\n>  }\n>  \n> -static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)\n> +static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n>  {\n> -\treturn for_each_ref_in(\"refs/bisect/good\", fn, cb_data);\n> +\treturn for_each_ref_in_submodule(submodule, \"refs/bisect/good\", fn, cb_data);\n>  }\n>  \n>  static void append_prune_data(const char ***prune_data, const char **av)\n> @@ -1466,6 +1466,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n>  {\n>  \tint i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;\n>  \tconst char **prune_data = NULL;\n> +\tconst char *submodule = NULL;\n> +\n> +\tif (opt)\n> +\t\tsubmodule = opt->submodule;\n>  \n>  \t/* First, search for \"--\" */\n>  \tseen_dashdash = 0;\n> @@ -1490,26 +1494,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n>  \t\t\tint opts;\n>  \n>  \t\t\tif (!strcmp(arg, \"--all\")) {\n> -\t\t\t\thandle_refs(revs, flags, for_each_ref);\n> -\t\t\t\thandle_refs(revs, flags, head_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags, for_each_ref_submodule);\n> +\t\t\t\thandle_refs(submodule, revs, flags, head_ref_submodule);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--branches\")) {\n> -\t\t\t\thandle_refs(revs, flags, for_each_branch_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags, for_each_branch_ref_submodule);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--bisect\")) {\n> -\t\t\t\thandle_refs(revs, flags, for_each_bad_bisect_ref);\n> -\t\t\t\thandle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags, for_each_bad_bisect_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);\n>  \t\t\t\trevs->bisect = 1;\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--tags\")) {\n> -\t\t\t\thandle_refs(revs, flags, for_each_tag_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags, for_each_tag_ref_submodule);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--remotes\")) {\n> -\t\t\t\thandle_refs(revs, flags, for_each_remote_ref);\n> +\t\t\t\thandle_refs(submodule, revs, flags, for_each_remote_ref_submodule);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!prefixcmp(arg, \"--glob=\")) {\n> diff --git a/revision.h b/revision.h\n> index 36fdf22..05659c6 100644\n> --- a/revision.h\n> +++ b/revision.h\n> @@ -151,6 +151,7 @@ extern volatile show_early_output_fn_t show_early_output;\n>  struct setup_revision_opt {\n>  \tconst char *def;\n>  \tvoid (*tweak)(struct rev_info *, struct setup_revision_opt *);\n> +\tconst char *submodule;\n>  };\n>  \n>  extern void init_revisions(struct rev_info *revs, const char *prefix);\n> -- \n> 1.7.2.rc1.217.g7dc0db.dirty\n"},{"id":"145020","messageId":"20100707133557.GA35678@book.hvoigt.net","threadId":"24309","inReplyTo":"201007070152.17802.johan@herland.net","subject":"Re: Re: [PATCH v2 4/4] implement automatic fast forward merge for submodules","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-07T13:35:58Z","receivedAt":"2010-07-07T13:35:58Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Hi,\n\nOn Wed, Jul 07, 2010 at 01:52:16AM +0200, Johan Herland wrote:\n> On Tuesday 06 July 2010, Heiko Voigt wrote:\n> [...]\n> \n> > +\t/* get all revisions that merge commit a */\n> > +\tsnprintf(merged_revision, sizeof(merged_revision), \"^%s\",\n> > +\t\t\tfind_unique_abbrev(a->object.sha1, 40));\n> \n> Why do you call find_unique_abbrev(..., 40) here?\n> Isn't sha1_to_hex(a->object.sha1) a better solution?\n\nBecause I did not know it better at the time I wrote this :) Will be\ncorrected.\n\n> > +\tinit_revisions(&revs, NULL);\n> > +\trev_opts.submodule = path;\n> > +\tsetup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);\n> > +\n> > +\t/* save all revisions from the above list that contain b */\n> > +\tif (prepare_revision_walk(&revs))\n> > +\t\tdie(\"revision walk setup failed\");\n> > +\twhile ((commit = get_revision(&revs)) != NULL) {\n> > +\t\tstruct object *o = &(commit->object);\n> > +\t\tif (in_merge_bases(b, (struct commit **) &o, 1)) {\n> \n> Why not s/(struct commit **) &o/&commit/ ?\n\nSimilar, because I needed objects for the array. Will be corrected.\n\n> > +\t (cd sub &&\n> > +\t  git rev-parse sub-d > ../expect) &&\n> > +\t test_cmp actual expect)\n> > +'\n> > +\n> > +test_expect_success 'merging should conflict for non fast-forward' '\n> > +\t(cd merge-search &&\n> > +\t git checkout -b test-nonforward b &&\n> > +\t (cd sub &&\n> > +\t  git rev-parse sub-d > ../expect) &&\n> > +\t test_must_fail \tgit merge c 2> actual  &&\n> > +\t grep $(<expect) actual > /dev/null &&\n> \n> I cannot find the \"$(<expect)\" construct anywhere else in out test scripts.\n> Is it portable? Should we maybe use \"$(cat expect)\" instead?\n\nI do not know. Just to be sure I will change it to cat.\n\n> Otherwise, it looks good to me. Thanks for the effort!\n\nThank you as well. The rest of the comments are already incorporated.\n\ncheers Heiko\n"},{"id":"145022","messageId":"20100707133723.GB35678@book.hvoigt.net","threadId":"24309","inReplyTo":"7veiff274b.fsf@alter.siamese.dyndns.org","subject":"Re: Re: [PATCH v2 3/4] extent setup_revisions() so it works with submodules","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2010-07-07T13:37:23Z","receivedAt":"2010-07-07T13:37:23Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Hi,\n\nOn Tue, Jul 06, 2010 at 10:28:20PM -0700, Junio C Hamano wrote:\n> Heiko Voigt <hvoigt@hvoigt.net> writes:\n> \n> > Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n> \n> This needs a bit more explanation.  \"Exten_d_ setup_revisions()\" is fine,\n> but \"works with submodules\"???  How? and what do the caller need to do?\n> \n> Something like...\n> \n>     Subject: setup_revisions(): allow walking history in a submodule\n> \n>     By passing the path to a submodule in opt->submodule, the function can\n>     be used to walk history in the named submodule repository, instead of\n>     the toplevel repository.\n\nWill take that. Thanks.\n\ncheers Heiko\n"},{"id":"145072","messageId":"7vy6dmreu1.fsf@alter.siamese.dyndns.org","threadId":"24309","inReplyTo":"20100707133557.GA35678@book.hvoigt.net","subject":"Re: [PATCH v2 4/4] implement automatic fast forward merge for submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-08T00:34:46Z","receivedAt":"2010-07-08T00:34:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heiko Voigt <hvoigt@hvoigt.net> writes:\n\n>> I cannot find the \"$(<expect)\" construct anywhere else in out test scripts.\n\nThanks for spotting and fixing.\n"}]}