Merge branch 'sb/submodule-embed-gitdir'
authorJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2017 23:24:27 +0000 (15:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2017 23:24:28 +0000 (15:24 -0800)
A new submodule helper "git submodule embedgitdirs" to make it
easier to move embedded .git/ directory for submodules in a
superproject to .git/modules/ (and point the latter with the former
that is turned into a "gitdir:" file) has been added.

* sb/submodule-embed-gitdir:
worktree: initialize return value for submodule_uses_worktrees
submodule: add absorb-git-dir function
move connect_work_tree_and_git_dir to dir.h
worktree: check if a submodule uses worktrees
test-lib-functions.sh: teach test_commit -C <dir>
submodule helper: support super prefix
submodule: use absolute path for computing relative path connecting

1  2 
builtin/submodule--helper.c
git-submodule.sh
git.c
submodule.c
submodule.h
t/test-lib-functions.sh
index 92fd676a2e3c5d4866eb5bed0d41503fc5796dfd,242d9911a6cea172b5ba72a4158eb3ecaa3743ee..df0d9c166f05b27469fbd6c1ddd208ac763667d7
@@@ -498,9 -498,9 +498,9 @@@ static int add_possible_reference_from_
  
        /*
         * If the alternate object store is another repository, try the
 -       * standard layout with .git/modules/<name>/objects
 +       * standard layout with .git/(modules/<name>)+/objects
         */
 -      if (ends_with(alt->path, ".git/objects")) {
 +      if (ends_with(alt->path, "/objects")) {
                char *sm_alternate;
                struct strbuf sb = STRBUF_INIT;
                struct strbuf err = STRBUF_INIT;
@@@ -583,7 -583,6 +583,7 @@@ static int module_clone(int argc, cons
        struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
 +      char *sm_alternate = NULL, *error_strategy = NULL;
  
        struct option module_clone_options[] = {
                OPT_STRING(0, "prefix", &prefix,
                die(_("could not get submodule directory for '%s'"), path);
        git_config_set_in_file(p, "core.worktree",
                               relative_path(path, sm_gitdir, &rel_path));
 +
 +      /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
 +      git_config_get_string("submodule.alternateLocation", &sm_alternate);
 +      if (sm_alternate)
 +              git_config_set_in_file(p, "submodule.alternateLocation",
 +                                         sm_alternate);
 +      git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
 +      if (error_strategy)
 +              git_config_set_in_file(p, "submodule.alternateErrorStrategy",
 +                                         error_strategy);
 +
 +      free(sm_alternate);
 +      free(error_strategy);
 +
        strbuf_release(&sb);
        strbuf_release(&rel_path);
        free(sm_gitdir);
@@@ -1091,21 -1076,62 +1091,62 @@@ static int resolve_remote_submodule_bra
        return 0;
  }
  
+ static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
+ {
+       int i;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES;
+       struct option embed_gitdir_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("path into the working tree")),
+               OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"),
+                       ABSORB_GITDIR_RECURSE_SUBMODULES),
+               OPT_END()
+       };
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper embed-git-dir [<path>...]"),
+               NULL
+       };
+       argc = parse_options(argc, argv, prefix, embed_gitdir_options,
+                            git_submodule_helper_usage, 0);
+       gitmodules_config();
+       git_config(submodule_config, NULL);
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+               return 1;
+       for (i = 0; i < list.nr; i++)
+               absorb_git_dir_into_superproject(prefix,
+                               list.entries[i]->name, flags);
+       return 0;
+ }
+ #define SUPPORT_SUPER_PREFIX (1<<0)
  struct cmd_struct {
        const char *cmd;
        int (*fn)(int, const char **, const char *);
+       unsigned option;
  };
  
  static struct cmd_struct commands[] = {
-       {"list", module_list},
-       {"name", module_name},
-       {"clone", module_clone},
-       {"update-clone", update_clone},
-       {"relative-path", resolve_relative_path},
-       {"resolve-relative-url", resolve_relative_url},
-       {"resolve-relative-url-test", resolve_relative_url_test},
-       {"init", module_init},
-       {"remote-branch", resolve_remote_submodule_branch}
+       {"list", module_list, 0},
+       {"name", module_name, 0},
+       {"clone", module_clone, 0},
+       {"update-clone", update_clone, 0},
+       {"relative-path", resolve_relative_path, 0},
+       {"resolve-relative-url", resolve_relative_url, 0},
+       {"resolve-relative-url-test", resolve_relative_url_test, 0},
+       {"init", module_init, 0},
+       {"remote-branch", resolve_remote_submodule_branch, 0},
+       {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
                die(_("submodule--helper subcommand must be "
                      "called with a subcommand"));
  
-       for (i = 0; i < ARRAY_SIZE(commands); i++)
-               if (!strcmp(argv[1], commands[i].cmd))
+       for (i = 0; i < ARRAY_SIZE(commands); i++) {
+               if (!strcmp(argv[1], commands[i].cmd)) {
+                       if (get_super_prefix() &&
+                           !(commands[i].option & SUPPORT_SUPER_PREFIX))
+                               die(_("%s doesn't support --super-prefix"),
+                                   commands[i].cmd);
                        return commands[i].fn(argc - 1, argv + 1, prefix);
+               }
+       }
  
        die(_("'%s' is not a valid submodule--helper "
              "subcommand"), argv[1]);
diff --combined git-submodule.sh
index 0a477b4c97fbe1764f3a631de75fba022da34d5e,9285b5c43d3658c9b72ebb6375a612c42fcd6868..554bd1c4943a5f2dd9aa9615dadc962bf6405d7a
@@@ -21,10 -21,14 +21,10 @@@ require_work_tre
  wt_prefix=$(git rev-parse --show-prefix)
  cd_to_toplevel
  
 -# Restrict ourselves to a vanilla subset of protocols; the URLs
 -# we get are under control of a remote repository, and we do not
 -# want them kicking off arbitrary git-remote-* programs.
 -#
 -# If the user has already specified a set of allowed protocols,
 -# we assume they know what they're doing and use that instead.
 -: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh}
 -export GIT_ALLOW_PROTOCOL
 +# Tell the rest of git that any URLs we get don't come
 +# directly from the user, so it can apply policy as appropriate.
 +GIT_PROTOCOL_FROM_USER=0
 +export GIT_PROTOCOL_FROM_USER
  
  command=
  branch=
@@@ -1127,6 -1131,11 +1127,11 @@@ cmd_sync(
        done
  }
  
+ cmd_absorbgitdirs()
+ {
+       git submodule--helper absorb-git-dirs --prefix "$wt_prefix" "$@"
+ }
  # This loop parses the command line arguments to find the
  # subcommand name to dispatch.  Parsing of the subcommand specific
  # options are primarily done by the subcommand implementations.
  while test $# != 0 && test -z "$command"
  do
        case "$1" in
-       add | foreach | init | deinit | update | status | summary | sync)
+       add | foreach | init | deinit | update | status | summary | sync | absorbgitdirs)
                command=$1
                ;;
        -q|--quiet)
diff --combined git.c
index dce529fcbfd6e5d8526b0e88b43f6c568876ce9d,98dcf6c5182a214191d0a2140e30fd301907d2e7..bbaa949e9cea975b0b5a8ef1799249bf0445dcaa
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -396,7 -396,7 +396,7 @@@ static struct cmd_struct commands[] = 
        { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
        { "annotate", cmd_annotate, RUN_SETUP },
        { "apply", cmd_apply, RUN_SETUP_GENTLY },
 -      { "archive", cmd_archive },
 +      { "archive", cmd_archive, RUN_SETUP_GENTLY },
        { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
        { "blame", cmd_blame, RUN_SETUP },
        { "branch", cmd_branch, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
        { "ls-tree", cmd_ls_tree, RUN_SETUP },
 -      { "mailinfo", cmd_mailinfo },
 +      { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY },
        { "mailsplit", cmd_mailsplit },
        { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
        { "merge-base", cmd_merge_base, RUN_SETUP },
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
-       { "submodule--helper", cmd_submodule__helper, RUN_SETUP },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX},
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP },
        { "unpack-file", cmd_unpack_file, RUN_SETUP },
@@@ -654,11 -654,6 +654,11 @@@ int cmd_main(int argc, const char **arg
        cmd = argv[0];
        if (!cmd)
                cmd = "git-help";
 +      else {
 +              const char *slash = find_last_dir_sep(cmd);
 +              if (slash)
 +                      cmd = slash + 1;
 +      }
  
        trace_command_performance(argv);
  
diff --combined submodule.c
index ece17315d671cf182f21c261d879c58f193cde09,45ccfb7ab4d512a317b950c99ae50bf491cc5c95..73521cdbb29abf7e3b1d9eb7293d16e94ec8ccce
@@@ -14,6 -14,7 +14,7 @@@
  #include "blob.h"
  #include "thread-utils.h"
  #include "quote.h"
+ #include "worktree.h"
  
  static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
  static int parallel_jobs = 1;
@@@ -500,67 -501,27 +501,67 @@@ static int has_remote(const char *refna
        return 1;
  }
  
 -static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
 +static int append_sha1_to_argv(const unsigned char sha1[20], void *data)
  {
 -      if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
 +      struct argv_array *argv = data;
 +      argv_array_push(argv, sha1_to_hex(sha1));
 +      return 0;
 +}
 +
 +static int check_has_commit(const unsigned char sha1[20], void *data)
 +{
 +      int *has_commit = data;
 +
 +      if (!lookup_commit_reference(sha1))
 +              *has_commit = 0;
 +
 +      return 0;
 +}
 +
 +static int submodule_has_commits(const char *path, struct sha1_array *commits)
 +{
 +      int has_commit = 1;
 +
 +      if (add_submodule_odb(path))
 +              return 0;
 +
 +      sha1_array_for_each_unique(commits, check_has_commit, &has_commit);
 +      return has_commit;
 +}
 +
 +static int submodule_needs_pushing(const char *path, struct sha1_array *commits)
 +{
 +      if (!submodule_has_commits(path, commits))
 +              /*
 +               * NOTE: We do consider it safe to return "no" here. The
 +               * correct answer would be "We do not know" instead of
 +               * "No push needed", but it is quite hard to change
 +               * the submodule pointer without having the submodule
 +               * around. If a user did however change the submodules
 +               * without having the submodule around, this indicates
 +               * an expert who knows what they are doing or a
 +               * maintainer integrating work from other people. In
 +               * both cases it should be safe to skip this check.
 +               */
                return 0;
  
        if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
                struct child_process cp = CHILD_PROCESS_INIT;
 -              const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
                struct strbuf buf = STRBUF_INIT;
                int needs_pushing = 0;
  
 -              argv[1] = sha1_to_hex(sha1);
 -              cp.argv = argv;
 +              argv_array_push(&cp.args, "rev-list");
 +              sha1_array_for_each_unique(commits, append_sha1_to_argv, &cp.args);
 +              argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL);
 +
                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 rev-list %s --not --remotes -n 1' command in submodule %s",
 -                              sha1_to_hex(sha1), path);
 +                      die("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s",
 +                                      path);
                if (strbuf_read(&buf, cp.out, 41))
                        needs_pushing = 1;
                finish_command(&cp);
        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 *needs_pushing = data;
 +      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;
 -              if (submodule_needs_pushing(p->two->path, p->two->oid.hash))
 -                      string_list_insert(needs_pushing, p->two->path);
 +              commits = submodule_commits(submodules, p->two->path);
 +              sha1_array_append(commits, p->two->oid.hash);
        }
  }
  
@@@ -615,63 -561,46 +616,63 @@@ static void find_unpushed_submodule_com
        diff_tree_combined_merge(commit, 1, &rev);
  }
  
 -int find_unpushed_submodules(unsigned char new_sha1[20],
 +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,
                const char *remotes_name, struct string_list *needs_pushing)
  {
        struct rev_info rev;
        struct commit *commit;
 -      const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
 -      int argc = ARRAY_SIZE(argv) - 1;
 -      char *sha1_copy;
 -
 -      struct strbuf remotes_arg = STRBUF_INIT;
 +      struct string_list submodules = STRING_LIST_INIT_DUP;
 +      struct string_list_item *submodule;
 +      struct argv_array argv = ARGV_ARRAY_INIT;
  
 -      strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
        init_revisions(&rev, NULL);
 -      sha1_copy = xstrdup(sha1_to_hex(new_sha1));
 -      argv[1] = sha1_copy;
 -      argv[3] = remotes_arg.buf;
 -      setup_revisions(argc, argv, &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);
 +      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, needs_pushing);
 +              find_unpushed_submodule_commits(commit, &submodules);
  
        reset_revision_walk();
 -      free(sha1_copy);
 -      strbuf_release(&remotes_arg);
 +      argv_array_clear(&argv);
 +
 +      for_each_string_list_item(submodule, &submodules) {
 +              struct sha1_array *commits = (struct sha1_array *) submodule->util;
 +
 +              if (submodule_needs_pushing(submodule->string, commits))
 +                      string_list_insert(needs_pushing, submodule->string);
 +      }
 +      free_submodules_sha1s(&submodules);
  
        return needs_pushing->nr;
  }
  
 -static int push_submodule(const char *path)
 +static int push_submodule(const char *path, int dry_run)
  {
        if (add_submodule_odb(path))
                return 1;
  
        if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
                struct child_process cp = CHILD_PROCESS_INIT;
 -              const char *argv[] = {"push", NULL};
 +              argv_array_push(&cp.args, "push");
 +              if (dry_run)
 +                      argv_array_push(&cp.args, "--dry-run");
  
 -              cp.argv = argv;
                prepare_submodule_repo_env(&cp.env_array);
                cp.git_cmd = 1;
                cp.no_stdin = 1;
        return 1;
  }
  
 -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
 +int push_unpushed_submodules(struct sha1_array *commits,
 +                           const char *remotes_name,
 +                           int dry_run)
  {
        int i, ret = 1;
        struct string_list needs_pushing = STRING_LIST_INIT_DUP;
  
 -      if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
 +      if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing))
                return 1;
  
        for (i = 0; i < needs_pushing.nr; i++) {
                const char *path = needs_pushing.items[i].string;
                fprintf(stderr, "Pushing submodule '%s'\n", path);
 -              if (!push_submodule(path)) {
 +              if (!push_submodule(path, dry_run)) {
                        fprintf(stderr, "Unable to push submodule '%s'\n", path);
                        ret = 0;
                }
@@@ -1296,30 -1223,6 +1297,6 @@@ int merge_submodule(unsigned char resul
        return 0;
  }
  
- /* Update gitfile and core.worktree setting to connect work tree and git dir */
- void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
- {
-       struct strbuf file_name = STRBUF_INIT;
-       struct strbuf rel_path = STRBUF_INIT;
-       const char *real_work_tree = xstrdup(real_path(work_tree));
-       /* Update gitfile */
-       strbuf_addf(&file_name, "%s/.git", work_tree);
-       write_file(file_name.buf, "gitdir: %s",
-                  relative_path(git_dir, real_work_tree, &rel_path));
-       /* Update core.worktree setting */
-       strbuf_reset(&file_name);
-       strbuf_addf(&file_name, "%s/config", git_dir);
-       git_config_set_in_file(file_name.buf, "core.worktree",
-                              relative_path(real_work_tree, git_dir,
-                                            &rel_path));
-       strbuf_release(&file_name);
-       strbuf_release(&rel_path);
-       free((void *)real_work_tree);
- }
  int parallel_submodules(void)
  {
        return parallel_jobs;
@@@ -1335,3 -1238,105 +1312,105 @@@ void prepare_submodule_repo_env(struct 
        }
        argv_array_push(out, "GIT_DIR=.git");
  }
+ /*
+  * Embeds a single submodules git directory into the superprojects git dir,
+  * non recursively.
+  */
+ static void relocate_single_git_dir_into_superproject(const char *prefix,
+                                                     const char *path)
+ {
+       char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
+       const char *new_git_dir;
+       const struct submodule *sub;
+       if (submodule_uses_worktrees(path))
+               die(_("relocate_gitdir for submodule '%s' with "
+                     "more than one worktree not supported"), path);
+       old_git_dir = xstrfmt("%s/.git", path);
+       if (read_gitfile(old_git_dir))
+               /* If it is an actual gitfile, it doesn't need migration. */
+               return;
+       real_old_git_dir = xstrdup(real_path(old_git_dir));
+       sub = submodule_from_path(null_sha1, path);
+       if (!sub)
+               die(_("could not lookup name for submodule '%s'"), path);
+       new_git_dir = git_path("modules/%s", sub->name);
+       if (safe_create_leading_directories_const(new_git_dir) < 0)
+               die(_("could not create directory '%s'"), new_git_dir);
+       real_new_git_dir = xstrdup(real_path(new_git_dir));
+       if (!prefix)
+               prefix = get_super_prefix();
+       fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
+               prefix ? prefix : "", path,
+               real_old_git_dir, real_new_git_dir);
+       relocate_gitdir(path, real_old_git_dir, real_new_git_dir);
+       free(old_git_dir);
+       free(real_old_git_dir);
+       free(real_new_git_dir);
+ }
+ /*
+  * Migrate the git directory of the submodule given by path from
+  * having its git directory within the working tree to the git dir nested
+  * in its superprojects git dir under modules/.
+  */
+ void absorb_git_dir_into_superproject(const char *prefix,
+                                     const char *path,
+                                     unsigned flags)
+ {
+       const char *sub_git_dir, *v;
+       char *real_sub_git_dir = NULL, *real_common_git_dir = NULL;
+       struct strbuf gitdir = STRBUF_INIT;
+       strbuf_addf(&gitdir, "%s/.git", path);
+       sub_git_dir = resolve_gitdir(gitdir.buf);
+       /* Not populated? */
+       if (!sub_git_dir)
+               goto out;
+       /* Is it already absorbed into the superprojects git dir? */
+       real_sub_git_dir = xstrdup(real_path(sub_git_dir));
+       real_common_git_dir = xstrdup(real_path(get_git_common_dir()));
+       if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v))
+               relocate_single_git_dir_into_superproject(prefix, path);
+       if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+               struct strbuf sb = STRBUF_INIT;
+               if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
+                       die("BUG: we don't know how to pass the flags down?");
+               if (get_super_prefix())
+                       strbuf_addstr(&sb, get_super_prefix());
+               strbuf_addstr(&sb, path);
+               strbuf_addch(&sb, '/');
+               cp.dir = path;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               argv_array_pushl(&cp.args, "--super-prefix", sb.buf,
+                                          "submodule--helper",
+                                          "absorb-git-dirs", NULL);
+               prepare_submodule_repo_env(&cp.env_array);
+               if (run_command(&cp))
+                       die(_("could not recurse into submodule '%s'"), path);
+               strbuf_release(&sb);
+       }
+ out:
+       strbuf_release(&gitdir);
+       free(real_sub_git_dir);
+       free(real_common_git_dir);
+ }
diff --combined submodule.h
index 23d76682b1ea123d040a29f6f9613c3e1794f87d,6229054b9928d742d4e1e40ef842aa82230b9375..b7576d6f43fee1d7a537bb61cbe02e5780dffa38
@@@ -3,7 -3,6 +3,7 @@@
  
  struct diff_options;
  struct argv_array;
 +struct sha1_array;
  
  enum {
        RECURSE_SUBMODULES_CHECK = -4,
@@@ -63,12 -62,9 +63,11 @@@ int submodule_uses_gitfile(const char *
  int ok_to_remove_submodule(const char *path);
  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 find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
 +int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name,
                struct string_list *needs_pushing);
 -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
 +extern int push_unpushed_submodules(struct sha1_array *commits,
 +                                  const char *remotes_name,
 +                                  int dry_run);
- void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
  int parallel_submodules(void);
  
  /*
@@@ -78,4 -74,8 +77,8 @@@
   */
  void prepare_submodule_repo_env(struct argv_array *out);
  
+ #define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0)
+ extern void absorb_git_dir_into_superproject(const char *prefix,
+                                            const char *path,
+                                            unsigned flags);
  #endif
diff --combined t/test-lib-functions.sh
index adab7f51f4c962967c90e3184853377feece2b1e,579e812506ec7cf0ea48aeb70991592fa7616f29..bd357704cce987afa79ec8fce038aa05f8c0a762
@@@ -157,16 -157,21 +157,21 @@@ debug () 
         GIT_TEST_GDB=1 "$@"
  }
  
- # Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]"
+ # Call test_commit with the arguments
+ # [-C <directory>] <message> [<file> [<contents> [<tag>]]]"
  #
  # This will commit a file with the given contents and the given commit
  # message, and tag the resulting commit with the given tag name.
  #
  # <file>, <contents>, and <tag> all default to <message>.
+ #
+ # If the first argument is "-C", the second argument is used as a path for
+ # the git invocations.
  
  test_commit () {
        notick= &&
        signoff= &&
+       indir= &&
        while test $# != 0
        do
                case "$1" in
                --signoff)
                        signoff="$1"
                        ;;
+               -C)
+                       indir="$2"
+                       shift
+                       ;;
                *)
                        break
                        ;;
                esac
                shift
        done &&
+       indir=${indir:+"$indir"/} &&
        file=${2:-"$1.t"} &&
-       echo "${3-$1}" > "$file" &&
-       git add "$file" &&
+       echo "${3-$1}" > "$indir$file" &&
+       git ${indir:+ -C "$indir"} add "$file" &&
        if test -z "$notick"
        then
                test_tick
        fi &&
-       git commit $signoff -m "$1" &&
-       git tag "${4:-$1}"
+       git ${indir:+ -C "$indir"} commit $signoff -m "$1" &&
+       git ${indir:+ -C "$indir"} tag "${4:-$1}"
  }
  
  # Call test_merge with the arguments "<message> <commit>", where <commit>
@@@ -994,17 -1004,3 +1004,17 @@@ test_copy_bytes () 
                }
        ' - "$1"
  }
 +
 +# run "$@" inside a non-git directory
 +nongit () {
 +      test -d non-repo ||
 +      mkdir non-repo ||
 +      return 1
 +
 +      (
 +              GIT_CEILING_DIRECTORIES=$(pwd) &&
 +              export GIT_CEILING_DIRECTORIES &&
 +              cd non-repo &&
 +              "$@"
 +      )
 +}