From: Junio C Hamano Date: Tue, 22 Aug 2017 17:29:00 +0000 (-0700) Subject: Merge branch 'bw/push-options-recursively-to-submodules' X-Git-Tag: v2.15.0-rc0~181 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/a49794d10826ef7cc9237abcf6474cd826de1063?ds=inline;hp=-c Merge branch 'bw/push-options-recursively-to-submodules' "git push --recurse-submodules $there HEAD:$target" was not propagated down to the submodules, but now it is. * bw/push-options-recursively-to-submodules: submodule--helper: teach push-check to handle HEAD --- a49794d10826ef7cc9237abcf6474cd826de1063 diff --combined builtin/submodule--helper.c index 84562ec833,825400dfd9..353e884d8d --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@@ -1,11 -1,10 +1,11 @@@ #include "builtin.h" +#include "repository.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "quote.h" #include "pathspec.h" #include "dir.h" -#include "utf8.h" #include "submodule.h" #include "submodule-config.h" #include "string-list.h" @@@ -234,7 -233,8 +234,7 @@@ static int module_list_compute(int argc int i, result = 0; char *ps_matched = NULL; parse_pathspec(pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_FULL, prefix, argv); if (pathspec->nr) @@@ -280,7 -280,7 +280,7 @@@ static void module_list_active(struct m for (i = 0; i < list->nr; i++) { const struct cache_entry *ce = list->entries[i]; - if (!is_submodule_initialized(ce->name)) + if (!is_submodule_active(the_repository, ce->name)) continue; ALLOC_GROW(active_modules.entries, @@@ -326,7 -326,7 +326,7 @@@ static int module_list(int argc, const printf("%06o %s %d\t", ce->ce_mode, oid_to_hex(&ce->oid), ce_stage(ce)); - utf8_fprintf(stdout, "%s\n", ce->name); + fprintf(stdout, "%s\n", ce->name); } return 0; } @@@ -350,7 -350,7 +350,7 @@@ static void init_submodule(const char * } else displaypath = xstrdup(path); - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) die(_("No url found for submodule path '%s' in .gitmodules"), @@@ -362,7 -362,7 +362,7 @@@ * * Set active flag for the submodule being initialized */ - if (!is_submodule_initialized(path)) { + if (!is_submodule_active(the_repository, path)) { strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.active", sub->name); git_config_set_gently(sb.buf, "true"); @@@ -376,12 -376,12 +376,12 @@@ strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.url", sub->name); if (git_config_get_string(sb.buf, &url)) { - url = xstrdup(sub->url); - - if (!url) + if (!sub->url) die(_("No url found for submodule path '%s' in .gitmodules"), displaypath); + url = xstrdup(sub->url); + /* Possibly a url relative to parent */ if (starts_with_dot_dot_slash(url) || starts_with_dot_slash(url)) { @@@ -476,7 -476,7 +476,7 @@@ static int module_name(int argc, const usage(_("git submodule--helper name ")); gitmodules_config(); - sub = submodule_from_path(null_sha1, argv[1]); + sub = submodule_from_path(&null_oid, argv[1]); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@@ -795,7 -795,7 +795,7 @@@ static int prepare_to_clone_next_submod goto cleanup; } - sub = submodule_from_path(null_sha1, ce->name); + sub = submodule_from_path(&null_oid, ce->name); if (suc->recursive_prefix) displaypath = relative_path(suc->recursive_prefix, @@@ -817,7 -817,7 +817,7 @@@ } /* Check if the submodule has been initialized. */ - if (!is_submodule_initialized(ce->name)) { + if (!is_submodule_active(the_repository, ce->name)) { next_submodule_warn_missing(suc, out, displaypath); goto cleanup; } @@@ -930,7 -930,7 +930,7 @@@ static int update_clone_task_finished(i const struct cache_entry *ce; struct submodule_update_clone *suc = suc_cb; - int *idxP = *(int**)idx_task_cb; + int *idxP = idx_task_cb; int idx = *idxP; free(idxP); @@@ -1038,7 -1038,7 +1038,7 @@@ static int update_clone(int argc, cons return 1; for_each_string_list_item(item, &suc.projectlines) - utf8_fprintf(stdout, "%s", item->string); + fprintf(stdout, "%s", item->string); return 0; } @@@ -1060,7 -1060,7 +1060,7 @@@ static const char *remote_submodule_bra gitmodules_config(); git_config(submodule_config, NULL); - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) return NULL; @@@ -1108,9 -1108,28 +1108,28 @@@ static int resolve_remote_submodule_bra static int push_check(int argc, const char **argv, const char *prefix) { struct remote *remote; + const char *superproject_head; + char *head; + int detached_head = 0; + struct object_id head_oid; - if (argc < 2) - die("submodule--helper push-check requires at least 1 argument"); + if (argc < 3) + die("submodule--helper push-check requires at least 2 arguments"); + + /* + * superproject's resolved head ref. + * if HEAD then the superproject is in a detached head state, otherwise + * it will be the resolved head ref. + */ + superproject_head = argv[1]; + argv++; + argc--; + /* Get the submodule's head ref and determine if it is detached */ + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + if (!strcmp(head, "HEAD")) + detached_head = 1; /* * The remote must be configured. @@@ -1133,18 -1152,30 +1152,30 @@@ if (rs->pattern || rs->matching) continue; - /* - * LHS must match a single ref - * NEEDSWORK: add logic to special case 'HEAD' once - * working with submodules in a detached head state - * ceases to be the norm. - */ - if (count_refspec_match(rs->src, local_refs, NULL) != 1) + /* LHS must match a single ref */ + switch (count_refspec_match(rs->src, local_refs, NULL)) { + case 1: + break; + case 0: + /* + * If LHS matches 'HEAD' then we need to ensure + * that it matches the same named branch + * checked out in the superproject. + */ + if (!strcmp(rs->src, "HEAD")) { + if (!detached_head && + !strcmp(head, superproject_head)) + break; + die("HEAD does not match the named branch in the superproject"); + } + default: die("src refspec '%s' must name a ref", rs->src); + } } free_refspec(refspec_nr, refspec); } + free(head); return 0; } @@@ -1189,11 -1220,11 +1220,11 @@@ static int absorb_git_dirs(int argc, co static int is_active(int argc, const char **argv, const char *prefix) { if (argc != 2) - die("submodule--helper is-active takes exactly 1 arguments"); + die("submodule--helper is-active takes exactly 1 argument"); gitmodules_config(); - return !is_submodule_initialized(argv[1]); + return !is_submodule_active(the_repository, argv[1]); } #define SUPPORT_SUPER_PREFIX (1<<0) @@@ -1222,8 -1253,9 +1253,8 @@@ static struct cmd_struct commands[] = int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; - if (argc < 2) - die(_("submodule--helper subcommand must be " - "called with a subcommand")); + if (argc < 2 || !strcmp(argv[1], "-h")) + usage("git submodule--helper "); for (i = 0; i < ARRAY_SIZE(commands); i++) { if (!strcmp(argv[1], commands[i].cmd)) { diff --combined submodule.c index 27de65a4a0,1c1339fff7..6ba15b4137 --- a/submodule.c +++ b/submodule.c @@@ -1,6 -1,4 +1,6 @@@ #include "cache.h" +#include "repository.h" +#include "config.h" #include "submodule-config.h" #include "submodule.h" #include "dir.h" @@@ -18,15 -16,14 +18,15 @@@ #include "quote.h" #include "remote.h" #include "worktree.h" +#include "parse-options.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; -static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF; static int parallel_jobs = 1; -static struct string_list changed_submodule_paths = STRING_LIST_INIT_NODUP; +static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP; static int initialized_fetch_ref_tips; -static struct sha1_array ref_tips_before_fetch; -static struct sha1_array ref_tips_after_fetch; +static struct oid_array ref_tips_before_fetch; +static struct oid_array ref_tips_after_fetch; /* * The following flag is set if the .gitmodules file is unmerged. We then @@@ -69,7 -66,7 +69,7 @@@ int update_path_in_gitmodules(const cha if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - submodule = submodule_from_path(null_sha1, oldpath); + submodule = submodule_from_path(&null_oid, oldpath); if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), oldpath); return -1; @@@ -103,7 -100,7 +103,7 @@@ int remove_path_from_gitmodules(const c if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - submodule = submodule_from_path(null_sha1, path); + submodule = submodule_from_path(&null_oid, path); if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), path); return -1; @@@ -147,7 -144,7 +147,7 @@@ done void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path) { - const struct submodule *submodule = submodule_from_path(null_sha1, path); + const struct submodule *submodule = submodule_from_path(&null_oid, path); if (submodule) { if (submodule->ignore) handle_ignore_submodules_arg(diffopt, submodule->ignore); @@@ -156,8 -153,7 +156,8 @@@ } } -int submodule_config(const char *var, const char *value, void *cb) +/* For loading from the .gitmodules file. */ +static int git_modules_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "submodule.fetchjobs")) { parallel_jobs = git_config_int(var, value); @@@ -173,56 -169,6 +173,56 @@@ return 0; } +/* Loads all submodule settings from the config. */ +int submodule_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) { + int v = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + config_update_recurse_submodules = v; + return 0; + } else { + return git_modules_config(var, value, cb); + } +} + +/* Cheap function that only determines if we're interested in submodules at all */ +int git_default_submodule_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) { + int v = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + config_update_recurse_submodules = v; + } + return 0; +} + +int option_parse_recurse_submodules_worktree_updater(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + config_update_recurse_submodules = RECURSE_SUBMODULES_OFF; + return 0; + } + if (arg) + config_update_recurse_submodules = + parse_update_recurse_submodules_arg(opt->long_name, + arg); + else + config_update_recurse_submodules = RECURSE_SUBMODULES_ON; + + return 0; +} + +void load_submodule_cache(void) +{ + if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF) + return; + + gitmodules_config(); + git_config(submodule_config, NULL); +} + void gitmodules_config(void) { const char *work_tree = get_git_work_tree(); @@@ -250,50 -196,39 +250,50 @@@ } if (!gitmodules_is_unmerged) - git_config_from_file(submodule_config, gitmodules_path.buf, NULL); + git_config_from_file(git_modules_config, + gitmodules_path.buf, NULL); strbuf_release(&gitmodules_path); } } -void gitmodules_config_sha1(const unsigned char *commit_sha1) +static int gitmodules_cb(const char *var, const char *value, void *data) +{ + struct repository *repo = data; + return submodule_config_option(repo, var, value); +} + +void repo_read_gitmodules(struct repository *repo) +{ + char *gitmodules_path = repo_worktree_path(repo, ".gitmodules"); + + git_config_from_file(gitmodules_cb, gitmodules_path, repo); + free(gitmodules_path); +} + +void gitmodules_config_oid(const struct object_id *commit_oid) { struct strbuf rev = STRBUF_INIT; - unsigned char sha1[20]; + struct object_id oid; - if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) { - git_config_from_blob_sha1(submodule_config, rev.buf, - sha1, NULL); + if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) { + git_config_from_blob_oid(submodule_config, rev.buf, + &oid, NULL); } strbuf_release(&rev); } /* - * NEEDSWORK: With the addition of different configuration options to determine - * if a submodule is of interests, the validity of this function's name comes - * into question. Once the dust has settled and more concrete terminology is - * decided upon, come up with a more proper name for this function. One - * potential candidate could be 'is_submodule_active()'. - * * Determine if a submodule has been initialized at a given 'path' */ -int is_submodule_initialized(const char *path) +int is_submodule_active(struct repository *repo, const char *path) { int ret = 0; char *key = NULL; char *value = NULL; const struct string_list *sl; - const struct submodule *module = submodule_from_path(null_sha1, path); + const struct submodule *module; + + module = submodule_from_cache(repo, &null_oid, path); /* early return if there isn't a path->module mapping */ if (!module) @@@ -301,14 -236,14 +301,14 @@@ /* submodule..active is set */ key = xstrfmt("submodule.%s.active", module->name); - if (!git_config_get_bool(key, &ret)) { + if (!repo_config_get_bool(repo, key, &ret)) { free(key); return ret; } free(key); /* submodule.active is set */ - sl = git_config_get_value_multi("submodule.active"); + sl = repo_config_get_value_multi(repo, "submodule.active"); if (sl) { struct pathspec ps; struct argv_array args = ARGV_ARRAY_INIT; @@@ -328,7 -263,7 +328,7 @@@ /* fallback to checking if the URL is set */ key = xstrfmt("submodule.%s.url", module->name); - ret = !git_config_get_string(key, &value); + ret = !repo_config_get_string(repo, key, &value); free(value); free(key); @@@ -347,69 -282,6 +347,69 @@@ int is_submodule_populated_gently(cons return ret; } +/* + * Dies if the provided 'prefix' corresponds to an unpopulated submodule + */ +void die_in_unpopulated_submodule(const struct index_state *istate, + const char *prefix) +{ + int i, prefixlen; + + if (!prefix) + return; + + prefixlen = strlen(prefix); + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + if (prefixlen <= ce_len) + continue; + if (strncmp(ce->name, prefix, ce_len)) + continue; + if (prefix[ce_len] != '/') + continue; + + die(_("in unpopulated submodule '%s'"), ce->name); + } +} + +/* + * Dies if any paths in the provided pathspec descends into a submodule + */ +void die_path_inside_submodule(const struct index_state *istate, + const struct pathspec *ps) +{ + int i, j; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + for (j = 0; j < ps->nr ; j++) { + const struct pathspec_item *item = &ps->items[j]; + + if (item->len <= ce_len) + continue; + if (item->match[ce_len] != '/') + continue; + if (strncmp(ce->name, item->match, ce_len)) + continue; + if (item->len == ce_len + 1) + continue; + + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } + } +} + int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst) { @@@ -575,8 -447,8 +575,8 @@@ static void show_submodule_header(FILE * Attempt to lookup the commit references, and determine if this is * a fast forward or fast backwards update. */ - *left = lookup_commit_reference(one->hash); - *right = lookup_commit_reference(two->hash); + *left = lookup_commit_reference(one); + *right = lookup_commit_reference(two); /* * Warn about missing commits in the submodule project, but only if @@@ -682,8 -554,7 +682,8 @@@ void show_submodule_inline_diff(FILE *f cp.no_stdin = 1; /* TODO: other options may need to be passed here. */ - argv_array_push(&cp.args, "diff"); + argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL); + argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix); if (DIFF_OPT_TST(o, REVERSE_DIFF)) { argv_array_pushf(&cp.args, "--src-prefix=%s%s/", @@@ -706,7 -577,6 +706,7 @@@ if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED)) argv_array_push(&cp.args, oid_to_hex(new)); + prepare_submodule_repo_env(&cp.env_array); if (run_command(&cp)) fprintf(f, "(diff failed)\n"); @@@ -725,6 -595,11 +725,6 @@@ void set_config_fetch_recurse_submodule config_fetch_recurse_submodules = value; } -void set_config_update_recurse_submodules(int value) -{ - config_update_recurse_submodules = value; -} - int should_update_submodules(void) { return config_update_recurse_submodules == RECURSE_SUBMODULES_ON; @@@ -738,95 -613,7 +738,95 @@@ const struct submodule *submodule_from_ if (!should_update_submodules()) return NULL; - return submodule_from_path(null_sha1, ce->name); + return submodule_from_path(&null_oid, ce->name); +} + +static struct oid_array *submodule_commits(struct string_list *submodules, + const char *path) +{ + struct string_list_item *item; + + item = string_list_insert(submodules, path); + if (item->util) + return (struct oid_array *) item->util; + + /* NEEDSWORK: should we have oid_array_init()? */ + item->util = xcalloc(1, sizeof(struct oid_array)); + return (struct oid_array *) item->util; +} + +static void collect_changed_submodules_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + struct string_list *changed = data; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + struct oid_array *commits; + if (!S_ISGITLINK(p->two->mode)) + continue; + + if (S_ISGITLINK(p->one->mode)) { + /* + * NEEDSWORK: We should honor the name configured in + * the .gitmodules file of the commit we are examining + * here to be able to correctly follow submodules + * being moved around. + */ + commits = submodule_commits(changed, p->two->path); + oid_array_append(commits, &p->two->oid); + } else { + /* Submodule is new or was moved here */ + /* + * NEEDSWORK: When the .git directories of submodules + * live inside the superprojects .git directory some + * day we should fetch new submodules directly into + * that location too when config or options request + * that so they can be checked out from there. + */ + continue; + } + } +} + +/* + * Collect the paths of submodules in 'changed' which have changed based on + * the revisions as specified in 'argv'. Each entry in 'changed' will also + * have a corresponding 'struct oid_array' (in the 'util' field) which lists + * what the submodule pointers were updated to during the change. + */ +static void collect_changed_submodules(struct string_list *changed, + struct argv_array *argv) +{ + struct rev_info rev; + const struct commit *commit; + + init_revisions(&rev, NULL); + setup_revisions(argv->argc, argv->argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + while ((commit = get_revision(&rev))) { + struct rev_info diff_rev; + + init_revisions(&diff_rev, NULL); + diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + diff_rev.diffopt.format_callback = collect_changed_submodules_cb; + diff_rev.diffopt.format_callback_data = changed; + diff_tree_combined_merge(commit, 1, &diff_rev); + } + + reset_revision_walk(); +} + +static void free_submodules_oids(struct string_list *submodules) +{ + struct string_list_item *item; + for_each_string_list_item(item, submodules) + oid_array_clear((struct oid_array *) item->util); + string_list_clear(submodules, 1); } static int has_remote(const char *refname, const struct object_id *oid, @@@ -835,69 -622,35 +835,69 @@@ return 1; } -static int append_sha1_to_argv(const unsigned char sha1[20], void *data) +static int append_oid_to_argv(const struct object_id *oid, void *data) { struct argv_array *argv = data; - argv_array_push(argv, sha1_to_hex(sha1)); + argv_array_push(argv, oid_to_hex(oid)); return 0; } -static int check_has_commit(const unsigned char sha1[20], void *data) +static int check_has_commit(const struct object_id *oid, void *data) { int *has_commit = data; - if (!lookup_commit_reference(sha1)) + if (!lookup_commit_reference(oid)) *has_commit = 0; return 0; } -static int submodule_has_commits(const char *path, struct sha1_array *commits) +static int submodule_has_commits(const char *path, struct oid_array *commits) { int has_commit = 1; + /* + * Perform a cheap, but incorrect check for the existence of 'commits'. + * This is done by adding the submodule's object store to the in-core + * object store, and then querying for each commit's existence. If we + * do not have the commit object anywhere, there is no chance we have + * it in the object store of the correct submodule and have it + * reachable from a ref, so we can fail early without spawning rev-list + * which is expensive. + */ if (add_submodule_odb(path)) return 0; - sha1_array_for_each_unique(commits, check_has_commit, &has_commit); + oid_array_for_each_unique(commits, check_has_commit, &has_commit); + + if (has_commit) { + /* + * Even if the submodule is checked out and the commit is + * present, make sure it exists in the submodule's object store + * and that it is reachable from a ref. + */ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + argv_array_pushl(&cp.args, "rev-list", "-n", "1", NULL); + oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args); + argv_array_pushl(&cp.args, "--not", "--all", NULL); + + prepare_submodule_repo_env(&cp.env_array); + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; + + if (capture_command(&cp, &out, GIT_MAX_HEXSZ + 1) || out.len) + has_commit = 0; + + strbuf_release(&out); + } + return has_commit; } -static int submodule_needs_pushing(const char *path, struct sha1_array *commits) +static int submodule_needs_pushing(const char *path, struct oid_array *commits) { if (!submodule_has_commits(path, commits)) /* @@@ -919,7 -672,7 +919,7 @@@ int needs_pushing = 0; argv_array_push(&cp.args, "rev-list"); - sha1_array_for_each_unique(commits, append_sha1_to_argv, &cp.args); + oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args); argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL); prepare_submodule_repo_env(&cp.env_array); @@@ -941,31 -694,91 +941,31 @@@ return 0; } -static struct sha1_array *submodule_commits(struct string_list *submodules, - const char *path) -{ - struct string_list_item *item; - - item = string_list_insert(submodules, path); - if (item->util) - return (struct sha1_array *) item->util; - - /* NEEDSWORK: should we have sha1_array_init()? */ - item->util = xcalloc(1, sizeof(struct sha1_array)); - return (struct sha1_array *) item->util; -} - -static void collect_submodules_from_diff(struct diff_queue_struct *q, - struct diff_options *options, - void *data) -{ - int i; - struct string_list *submodules = data; - - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - struct sha1_array *commits; - if (!S_ISGITLINK(p->two->mode)) - continue; - commits = submodule_commits(submodules, p->two->path); - sha1_array_append(commits, p->two->oid.hash); - } -} - -static void find_unpushed_submodule_commits(struct commit *commit, - struct string_list *needs_pushing) -{ - struct rev_info rev; - - init_revisions(&rev, NULL); - rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = collect_submodules_from_diff; - rev.diffopt.format_callback_data = needs_pushing; - diff_tree_combined_merge(commit, 1, &rev); -} - -static void free_submodules_sha1s(struct string_list *submodules) -{ - struct string_list_item *item; - for_each_string_list_item(item, submodules) - sha1_array_clear((struct sha1_array *) item->util); - string_list_clear(submodules, 1); -} - -int find_unpushed_submodules(struct sha1_array *commits, +int find_unpushed_submodules(struct oid_array *commits, const char *remotes_name, struct string_list *needs_pushing) { - struct rev_info rev; - struct commit *commit; struct string_list submodules = STRING_LIST_INIT_DUP; struct string_list_item *submodule; struct argv_array argv = ARGV_ARRAY_INIT; - init_revisions(&rev, NULL); - /* argv.argv[0] will be ignored by setup_revisions */ argv_array_push(&argv, "find_unpushed_submodules"); - sha1_array_for_each_unique(commits, append_sha1_to_argv, &argv); + oid_array_for_each_unique(commits, append_oid_to_argv, &argv); argv_array_push(&argv, "--not"); argv_array_pushf(&argv, "--remotes=%s", remotes_name); - setup_revisions(argv.argc, argv.argv, &rev, NULL); - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); - - while ((commit = get_revision(&rev)) != NULL) - find_unpushed_submodule_commits(commit, &submodules); - - reset_revision_walk(); - argv_array_clear(&argv); + collect_changed_submodules(&submodules, &argv); for_each_string_list_item(submodule, &submodules) { - struct sha1_array *commits = (struct sha1_array *) submodule->util; + struct oid_array *commits = submodule->util; + const char *path = submodule->string; - if (submodule_needs_pushing(submodule->string, commits)) - string_list_insert(needs_pushing, submodule->string); + if (submodule_needs_pushing(path, commits)) + string_list_insert(needs_pushing, path); } - free_submodules_sha1s(&submodules); + + free_submodules_oids(&submodules); + argv_array_clear(&argv); return needs_pushing->nr; } @@@ -1015,7 -828,8 +1015,8 @@@ static int push_submodule(const char *p * Perform a check in the submodule to see if the remote and refspec work. * Die if the submodule can't be pushed. */ - static void submodule_push_check(const char *path, const struct remote *remote, + static void submodule_push_check(const char *path, const char *head, + const struct remote *remote, const char **refspec, int refspec_nr) { struct child_process cp = CHILD_PROCESS_INIT; @@@ -1023,6 -837,7 +1024,7 @@@ argv_array_push(&cp.args, "submodule--helper"); argv_array_push(&cp.args, "push-check"); + argv_array_push(&cp.args, head); argv_array_push(&cp.args, remote->name); for (i = 0; i < refspec_nr; i++) @@@ -1043,7 -858,7 +1045,7 @@@ die("process for submodule '%s' failed", path); } -int push_unpushed_submodules(struct sha1_array *commits, +int push_unpushed_submodules(struct oid_array *commits, const struct remote *remote, const char **refspec, int refspec_nr, const struct string_list *push_options, @@@ -1061,10 -876,20 +1063,20 @@@ * won't be propagated due to the remote being unconfigured (e.g. a URL * instead of a remote name). */ - if (remote->origin != REMOTE_UNCONFIGURED) + if (remote->origin != REMOTE_UNCONFIGURED) { + char *head; + struct object_id head_oid; + + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + for (i = 0; i < needs_pushing.nr; i++) submodule_push_check(needs_pushing.items[i].string, - remote, refspec, refspec_nr); + head, remote, + refspec, refspec_nr); + free(head); + } /* Actually push the submodules */ for (i = 0; i < needs_pushing.nr; i++) { @@@ -1082,88 -907,131 +1094,88 @@@ return ret; } -static int is_submodule_commit_present(const char *path, unsigned char sha1[20]) -{ - int is_present = 0; - if (!add_submodule_odb(path) && lookup_commit_reference(sha1)) { - /* Even if the submodule is checked out and the commit is - * present, make sure it is reachable from a ref. */ - struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = {"rev-list", "-n", "1", NULL, "--not", "--all", NULL}; - struct strbuf buf = STRBUF_INIT; - - argv[3] = sha1_to_hex(sha1); - cp.argv = argv; - prepare_submodule_repo_env(&cp.env_array); - cp.git_cmd = 1; - cp.no_stdin = 1; - cp.dir = path; - if (!capture_command(&cp, &buf, 1024) && !buf.len) - is_present = 1; - - strbuf_release(&buf); - } - return is_present; -} - -static void submodule_collect_changed_cb(struct diff_queue_struct *q, - struct diff_options *options, - void *data) -{ - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (!S_ISGITLINK(p->two->mode)) - continue; - - if (S_ISGITLINK(p->one->mode)) { - /* NEEDSWORK: We should honor the name configured in - * the .gitmodules file of the commit we are examining - * here to be able to correctly follow submodules - * being moved around. */ - struct string_list_item *path; - path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path); - if (!path && !is_submodule_commit_present(p->two->path, p->two->oid.hash)) - string_list_append(&changed_submodule_paths, xstrdup(p->two->path)); - } else { - /* Submodule is new or was moved here */ - /* NEEDSWORK: When the .git directories of submodules - * live inside the superprojects .git directory some - * day we should fetch new submodules directly into - * that location too when config or options request - * that so they can be checked out from there. */ - continue; - } - } -} - -static int add_sha1_to_array(const char *ref, const struct object_id *oid, - int flags, void *data) +static int append_oid_to_array(const char *ref, const struct object_id *oid, + int flags, void *data) { - sha1_array_append(data, oid->hash); + struct oid_array *array = data; + oid_array_append(array, oid); return 0; } -void check_for_new_submodule_commits(unsigned char new_sha1[20]) +void check_for_new_submodule_commits(struct object_id *oid) { if (!initialized_fetch_ref_tips) { - for_each_ref(add_sha1_to_array, &ref_tips_before_fetch); + for_each_ref(append_oid_to_array, &ref_tips_before_fetch); initialized_fetch_ref_tips = 1; } - sha1_array_append(&ref_tips_after_fetch, new_sha1); -} - -static int add_sha1_to_argv(const unsigned char sha1[20], void *data) -{ - argv_array_push(data, sha1_to_hex(sha1)); - return 0; + oid_array_append(&ref_tips_after_fetch, oid); } static void calculate_changed_submodule_paths(void) { - struct rev_info rev; - struct commit *commit; struct argv_array argv = ARGV_ARRAY_INIT; + struct string_list changed_submodules = STRING_LIST_INIT_DUP; + const struct string_list_item *item; /* No need to check if there are no submodules configured */ if (!submodule_from_path(NULL, NULL)) return; - init_revisions(&rev, NULL); argv_array_push(&argv, "--"); /* argv[0] program name */ - sha1_array_for_each_unique(&ref_tips_after_fetch, - add_sha1_to_argv, &argv); + oid_array_for_each_unique(&ref_tips_after_fetch, + append_oid_to_argv, &argv); argv_array_push(&argv, "--not"); - sha1_array_for_each_unique(&ref_tips_before_fetch, - add_sha1_to_argv, &argv); - setup_revisions(argv.argc, argv.argv, &rev, NULL); - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + oid_array_for_each_unique(&ref_tips_before_fetch, + append_oid_to_argv, &argv); /* * Collect all submodules (whether checked out or not) for which new * commits have been recorded upstream in "changed_submodule_paths". */ - while ((commit = get_revision(&rev))) { - struct commit_list *parent = commit->parents; - while (parent) { - struct diff_options diff_opts; - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.output_format |= DIFF_FORMAT_CALLBACK; - diff_opts.format_callback = submodule_collect_changed_cb; - diff_setup_done(&diff_opts); - diff_tree_sha1(parent->item->object.oid.hash, commit->object.oid.hash, "", &diff_opts); - diffcore_std(&diff_opts); - diff_flush(&diff_opts); - parent = parent->next; - } + collect_changed_submodules(&changed_submodules, &argv); + + for_each_string_list_item(item, &changed_submodules) { + struct oid_array *commits = item->util; + const char *path = item->string; + + if (!submodule_has_commits(path, commits)) + string_list_append(&changed_submodule_paths, path); } + free_submodules_oids(&changed_submodules); argv_array_clear(&argv); - sha1_array_clear(&ref_tips_before_fetch); - sha1_array_clear(&ref_tips_after_fetch); + oid_array_clear(&ref_tips_before_fetch); + oid_array_clear(&ref_tips_after_fetch); initialized_fetch_ref_tips = 0; } +int submodule_touches_in_range(struct object_id *excl_oid, + struct object_id *incl_oid) +{ + struct string_list subs = STRING_LIST_INIT_DUP; + struct argv_array args = ARGV_ARRAY_INIT; + int ret; + + gitmodules_config(); + /* No need to check if there are no submodules configured */ + if (!submodule_from_path(NULL, NULL)) + return 0; + + argv_array_push(&args, "--"); /* args[0] program name */ + argv_array_push(&args, oid_to_hex(incl_oid)); + argv_array_push(&args, "--not"); + argv_array_push(&args, oid_to_hex(excl_oid)); + + collect_changed_submodules(&subs, &args); + ret = subs.nr; + + argv_array_clear(&args); + + free_submodules_oids(&subs); + return ret; +} + struct submodule_parallel_fetch { int count; struct argv_array args; @@@ -1192,9 -1060,9 +1204,9 @@@ static int get_next_submodule(struct ch if (!S_ISGITLINK(ce->ce_mode)) continue; - submodule = submodule_from_path(null_sha1, ce->name); + submodule = submodule_from_path(&null_oid, ce->name); if (!submodule) - submodule = submodule_from_name(null_sha1, ce->name); + submodule = submodule_from_name(&null_oid, ce->name); default_argv = "yes"; if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) { @@@ -1321,78 -1189,67 +1333,78 @@@ out unsigned is_submodule_modified(const char *path, int ignore_untracked) { - ssize_t len; struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = { - "status", - "--porcelain", - NULL, - NULL, - }; struct strbuf buf = STRBUF_INIT; + FILE *fp; unsigned dirty_submodule = 0; - const char *line, *next_line; const char *git_dir; + int ignore_cp_exit_code = 0; strbuf_addf(&buf, "%s/.git", path); git_dir = read_gitfile(buf.buf); if (!git_dir) git_dir = buf.buf; - if (!is_directory(git_dir)) { + if (!is_git_directory(git_dir)) { + if (is_directory(git_dir)) + die(_("'%s' not recognized as a git repository"), git_dir); strbuf_release(&buf); /* The submodule is not checked out, so it is not modified */ return 0; - } strbuf_reset(&buf); + argv_array_pushl(&cp.args, "status", "--porcelain=2", NULL); if (ignore_untracked) - argv[2] = "-uno"; + argv_array_push(&cp.args, "-uno"); - cp.argv = argv; prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; if (start_command(&cp)) - die("Could not run 'git status --porcelain' in submodule %s", path); + die("Could not run 'git status --porcelain=2' in submodule %s", path); - len = strbuf_read(&buf, cp.out, 1024); - line = buf.buf; - while (len > 2) { - if ((line[0] == '?') && (line[1] == '?')) { + fp = xfdopen(cp.out, "r"); + while (strbuf_getwholeline(&buf, fp, '\n') != EOF) { + /* regular untracked files */ + if (buf.buf[0] == '?') dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED; - if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - break; - } else { - dirty_submodule |= DIRTY_SUBMODULE_MODIFIED; - if (ignore_untracked || - (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)) - break; + + if (buf.buf[0] == 'u' || + buf.buf[0] == '1' || + buf.buf[0] == '2') { + /* T = line type, XY = status, SSSS = submodule state */ + if (buf.len < strlen("T XY SSSS")) + die("BUG: invalid status --porcelain=2 line %s", + buf.buf); + + if (buf.buf[5] == 'S' && buf.buf[8] == 'U') + /* nested untracked file */ + dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED; + + if (buf.buf[0] == 'u' || + buf.buf[0] == '2' || + memcmp(buf.buf + 5, "S..U", 4)) + /* other change */ + dirty_submodule |= DIRTY_SUBMODULE_MODIFIED; } - next_line = strchr(line, '\n'); - if (!next_line) + + if ((dirty_submodule & DIRTY_SUBMODULE_MODIFIED) && + ((dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) || + ignore_untracked)) { + /* + * We're not interested in any further information from + * the child any more, neither output nor its exit code. + */ + ignore_cp_exit_code = 1; break; - next_line++; - len -= (next_line - line); - line = next_line; + } } - close(cp.out); + fclose(fp); - if (finish_command(&cp)) - die("'git status --porcelain' failed in submodule %s", path); + if (finish_command(&cp) && !ignore_cp_exit_code) + die("'git status --porcelain=2' failed in submodule %s", path); strbuf_release(&buf); return dirty_submodule; @@@ -1472,7 -1329,7 +1484,7 @@@ int bad_to_remove_submodule(const char cp.dir = path; if (start_command(&cp)) { if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) - die(_("could not start 'git status in submodule '%s'"), + die(_("could not start 'git status' in submodule '%s'"), path); ret = -1; goto out; @@@ -1485,7 -1342,7 +1497,7 @@@ if (finish_command(&cp)) { if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) - die(_("could not run 'git status in submodule '%s'"), + die(_("could not run 'git status' in submodule '%s'"), path); ret = -1; } @@@ -1506,7 -1363,7 +1518,7 @@@ static int submodule_has_dirty_index(co { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; argv_array_pushl(&cp.args, "diff-index", "--quiet", @@@ -1523,7 -1380,7 +1535,7 @@@ static void submodule_reset_index(const char *path) { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@@ -1552,25 -1409,8 +1564,25 @@@ int submodule_move_head(const char *pat int ret = 0; struct child_process cp = CHILD_PROCESS_INIT; const struct submodule *sub; + int *error_code_ptr, error_code; + + if (!is_submodule_active(the_repository, path)) + return 0; - sub = submodule_from_path(null_sha1, path); + if (flags & SUBMODULE_MOVE_HEAD_FORCE) + /* + * Pass non NULL pointer to is_submodule_populated_gently + * to prevent die()-ing. We'll use connect_work_tree_and_git_dir + * to fixup the submodule in the force case later. + */ + error_code_ptr = &error_code; + else + error_code_ptr = NULL; + + if (old && !is_submodule_populated_gently(path, error_code_ptr)) + return 0; + + sub = submodule_from_path(&null_oid, path); if (!sub) die("BUG: could not get submodule information for '%s'", path); @@@ -1587,24 -1427,18 +1599,24 @@@ absorb_git_dir_into_superproject("", path, ABSORB_GITDIR_RECURSE_SUBMODULES); } else { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s/modules/%s", + char *gitdir = xstrfmt("%s/modules/%s", get_git_common_dir(), sub->name); - connect_work_tree_and_git_dir(path, sb.buf); - strbuf_release(&sb); + connect_work_tree_and_git_dir(path, gitdir); + free(gitdir); /* make sure the index is clean as well */ submodule_reset_index(path); } + + if (old && (flags & SUBMODULE_MOVE_HEAD_FORCE)) { + char *gitdir = xstrfmt("%s/modules/%s", + get_git_common_dir(), sub->name); + connect_work_tree_and_git_dir(path, gitdir); + free(gitdir); + } } - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@@ -1612,7 -1446,7 +1624,7 @@@ argv_array_pushf(&cp.args, "--super-prefix=%s%s/", get_super_prefix_or_empty(), path); - argv_array_pushl(&cp.args, "read-tree", NULL); + argv_array_pushl(&cp.args, "read-tree", "--recurse-submodules", NULL); if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN) argv_array_push(&cp.args, "-n"); @@@ -1634,16 -1468,16 +1646,16 @@@ if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) { if (new) { - struct child_process cp1 = CHILD_PROCESS_INIT; + child_process_init(&cp); /* also set the HEAD accordingly */ - cp1.git_cmd = 1; - cp1.no_stdin = 1; - cp1.dir = path; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; - argv_array_pushl(&cp1.args, "update-ref", "HEAD", - new ? new : EMPTY_TREE_SHA1_HEX, NULL); + prepare_submodule_repo_env(&cp.env_array); + argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL); - if (run_command(&cp1)) { + if (run_command(&cp)) { ret = -1; goto out; } @@@ -1680,7 -1514,7 +1692,7 @@@ static int find_first_merges(struct obj memset(&rev_opts, 0, sizeof(rev_opts)); /* get all revisions that merge commit a */ - snprintf(merged_revision, sizeof(merged_revision), "^%s", + xsnprintf(merged_revision, sizeof(merged_revision), "^%s", oid_to_hex(&a->object.oid)); init_revisions(&revs, NULL); rev_opts.submodule = path; @@@ -1733,9 -1567,9 +1745,9 @@@ static void print_commit(struct commit #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], int search) +int merge_submodule(struct object_id *result, const char *path, + const struct object_id *base, const struct object_id *a, + const struct object_id *b, int search) { struct commit *commit_base, *commit_a, *commit_b; int parent_count; @@@ -1744,14 -1578,14 +1756,14 @@@ int i; /* store a in result in case we fail */ - hashcpy(result, a); + oidcpy(result, a); /* we can not handle deletion conflicts */ - if (is_null_sha1(base)) + if (is_null_oid(base)) return 0; - if (is_null_sha1(a)) + if (is_null_oid(a)) return 0; - if (is_null_sha1(b)) + if (is_null_oid(b)) return 0; if (add_submodule_odb(path)) { @@@ -1775,11 -1609,11 +1787,11 @@@ /* Case #1: a is contained in b or vice versa */ if (in_merge_bases(commit_a, commit_b)) { - hashcpy(result, b); + oidcpy(result, b); return 1; } if (in_merge_bases(commit_b, commit_a)) { - hashcpy(result, a); + oidcpy(result, a); return 1; } @@@ -1852,7 -1686,7 +1864,7 @@@ static void relocate_single_git_dir_int real_old_git_dir = real_pathdup(old_git_dir, 1); - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) die(_("could not lookup name for submodule '%s'"), path); @@@ -1908,7 -1742,7 +1920,7 @@@ void absorb_git_dir_into_superproject(c * superproject did not rewrite the git file links yet, * fix it now. */ - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) die(_("could not lookup name for submodule '%s'"), path); connect_work_tree_and_git_dir(path, @@@ -2032,34 -1866,3 +2044,34 @@@ const char *get_superproject_working_tr return ret; } + +int submodule_to_gitdir(struct strbuf *buf, const char *submodule) +{ + const struct submodule *sub; + const char *git_dir; + int ret = 0; + + strbuf_reset(buf); + strbuf_addstr(buf, submodule); + strbuf_complete(buf, '/'); + strbuf_addstr(buf, ".git"); + + git_dir = read_gitfile(buf->buf); + if (git_dir) { + strbuf_reset(buf); + strbuf_addstr(buf, git_dir); + } + if (!is_git_directory(buf->buf)) { + gitmodules_config(); + sub = submodule_from_path(&null_oid, submodule); + if (!sub) { + ret = -1; + goto cleanup; + } + strbuf_reset(buf); + strbuf_git_path(buf, "%s/%s", "modules", sub->name); + } + +cleanup: + return ret; +} diff --combined t/t5531-deep-submodule-push.sh index beff65b8ac,9e4410bd37..0f84a53146 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@@ -1,6 -1,6 +1,6 @@@ #!/bin/sh -test_description='unpack-objects' +test_description='test push with submodules' . ./test-lib.sh @@@ -27,7 -27,7 +27,7 @@@ test_expect_success setup ) ' -test_expect_success push ' +test_expect_success 'push works with recorded gitlink' ' ( cd work && git push ../pub.git master @@@ -121,27 -121,6 +121,27 @@@ test_expect_success 'push succeeds if s git fetch ../pub.git && git diff --quiet FETCH_HEAD master && # Check that the submodule commit got there too + cd gar/bage && + git diff --quiet origin/master master + ) +' + +test_expect_success 'push succeeds if submodule commit not on remote but using auto-on-demand via submodule.recurse config' ' + ( + cd work/gar/bage && + >recurse-on-demand-from-submodule-recurse-config && + git add recurse-on-demand-from-submodule-recurse-config && + git commit -m "Recurse submodule.recurse from config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse submodule.recurse from config for gar/bage" && + git -c submodule.recurse push ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there too cd gar/bage && git diff --quiet origin/master master ) @@@ -533,7 -512,8 +533,8 @@@ test_expect_success 'push propagating r # Fails when refspec includes an object id test_must_fail git -C work push --recurse-submodules=on-demand origin \ "$(git -C work rev-parse branch2):refs/heads/branch2" && - # Fails when refspec includes 'HEAD' as it is unsupported at this time + # Fails when refspec includes HEAD and parent and submodule do not + # have the same named branch checked out test_must_fail git -C work push --recurse-submodules=on-demand origin \ HEAD:refs/heads/branch2 && @@@ -548,4 -528,26 +549,26 @@@ test_cmp expected_pub actual_pub ' + test_expect_success 'push propagating HEAD refspec to a submodule' ' + git -C work/gar/bage checkout branch2 && + > work/gar/bage/junk12 && + git -C work/gar/bage add junk12 && + git -C work/gar/bage commit -m "Twelfth junk" && + + git -C work checkout branch2 && + git -C work add gar/bage && + git -C work commit -m "updating gar/bage in branch2" && + + # Passes since the superproject and submodules HEAD are both on branch2 + git -C work push --recurse-submodules=on-demand origin \ + HEAD:refs/heads/branch2 && + + git -C submodule.git rev-parse branch2 >actual_submodule && + git -C pub.git rev-parse branch2 >actual_pub && + git -C work/gar/bage rev-parse branch2 >expected_submodule && + git -C work rev-parse branch2 >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub + ' + test_done