Merge branch 'sb/show-diff-for-submodule-in-diff-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 17 Apr 2017 06:29:32 +0000 (23:29 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 17 Apr 2017 06:29:32 +0000 (23:29 -0700)
"git diff --submodule=diff" learned to work better in a project
with a submodule that in turn has its own submodules.

* sb/show-diff-for-submodule-in-diff-fix:
diff: submodule inline diff to initialize env array.

1  2 
submodule.c
diff --combined submodule.c
index 83b13b8ff5fa31a213dcd1d87f67a919441494fa,17de8e83588538ce5cc51dcb992e224742643d78..7c3c4b17fb10b45536bc0680b85d19a8b811f919
  #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 config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static int parallel_jobs = 1;
  static struct string_list changed_submodule_paths = STRING_LIST_INIT_NODUP;
  static int initialized_fetch_ref_tips;
@@@ -200,87 -198,6 +200,87 @@@ void gitmodules_config(void
        }
  }
  
 +void gitmodules_config_sha1(const unsigned char *commit_sha1)
 +{
 +      struct strbuf rev = STRBUF_INIT;
 +      unsigned char sha1[20];
 +
 +      if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
 +              git_config_from_blob_sha1(submodule_config, rev.buf,
 +                                        sha1, 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 ret = 0;
 +      char *key = NULL;
 +      char *value = NULL;
 +      const struct string_list *sl;
 +      const struct submodule *module = submodule_from_path(null_sha1, path);
 +
 +      /* early return if there isn't a path->module mapping */
 +      if (!module)
 +              return 0;
 +
 +      /* submodule.<name>.active is set */
 +      key = xstrfmt("submodule.%s.active", module->name);
 +      if (!git_config_get_bool(key, &ret)) {
 +              free(key);
 +              return ret;
 +      }
 +      free(key);
 +
 +      /* submodule.active is set */
 +      sl = git_config_get_value_multi("submodule.active");
 +      if (sl) {
 +              struct pathspec ps;
 +              struct argv_array args = ARGV_ARRAY_INIT;
 +              const struct string_list_item *item;
 +
 +              for_each_string_list_item(item, sl) {
 +                      argv_array_push(&args, item->string);
 +              }
 +
 +              parse_pathspec(&ps, 0, 0, NULL, args.argv);
 +              ret = match_pathspec(&ps, path, strlen(path), 0, NULL, 1);
 +
 +              argv_array_clear(&args);
 +              clear_pathspec(&ps);
 +              return ret;
 +      }
 +
 +      /* fallback to checking if the URL is set */
 +      key = xstrfmt("submodule.%s.url", module->name);
 +      ret = !git_config_get_string(key, &value);
 +
 +      free(value);
 +      free(key);
 +      return ret;
 +}
 +
 +int is_submodule_populated_gently(const char *path, int *return_error_code)
 +{
 +      int ret = 0;
 +      char *gitdir = xstrfmt("%s/.git", path);
 +
 +      if (resolve_gitdir_gently(gitdir, return_error_code))
 +              ret = 1;
 +
 +      free(gitdir);
 +      return ret;
 +}
 +
  int parse_submodule_update_strategy(const char *value,
                struct submodule_update_strategy *dst)
  {
@@@ -390,23 -307,6 +390,23 @@@ static void print_submodule_summary(str
        strbuf_release(&sb);
  }
  
 +static void prepare_submodule_repo_env_no_git_dir(struct argv_array *out)
 +{
 +      const char * const *var;
 +
 +      for (var = local_repo_env; *var; var++) {
 +              if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
 +                      argv_array_push(out, *var);
 +      }
 +}
 +
 +void prepare_submodule_repo_env(struct argv_array *out)
 +{
 +      prepare_submodule_repo_env_no_git_dir(out);
 +      argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
 +                       DEFAULT_GIT_DIR_ENVIRONMENT);
 +}
 +
  /* Helper function to display the submodule header line prior to the full
   * summary output. If it can locate the submodule objects directory it will
   * attempt to lookup both the left and right commits and put them into the
@@@ -576,6 -476,7 +576,7 @@@ void show_submodule_inline_diff(FILE *f
        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");
  
@@@ -594,27 -495,6 +595,27 @@@ 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;
 +}
 +
 +const struct submodule *submodule_from_ce(const struct cache_entry *ce)
 +{
 +      if (!S_ISGITLINK(ce->ce_mode))
 +              return NULL;
 +
 +      if (!should_update_submodules())
 +              return NULL;
 +
 +      return submodule_from_path(null_sha1, ce->name);
 +}
 +
  static int has_remote(const char *refname, const struct object_id *oid,
                      int flags, void *cb_data)
  {
@@@ -1213,209 -1093,45 +1214,209 @@@ int submodule_uses_gitfile(const char *
        return 1;
  }
  
 -int ok_to_remove_submodule(const char *path)
 +/*
 + * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data
 + * when doing so.
 + *
 + * Return 1 if we'd lose data, return 0 if the removal is fine,
 + * and negative values for errors.
 + */
 +int bad_to_remove_submodule(const char *path, unsigned flags)
  {
        ssize_t len;
        struct child_process cp = CHILD_PROCESS_INIT;
 -      const char *argv[] = {
 -              "status",
 -              "--porcelain",
 -              "-u",
 -              "--ignore-submodules=none",
 -              NULL,
 -      };
        struct strbuf buf = STRBUF_INIT;
 -      int ok_to_remove = 1;
 +      int ret = 0;
  
        if (!file_exists(path) || is_empty_dir(path))
 -              return 1;
 +              return 0;
  
        if (!submodule_uses_gitfile(path))
 -              return 0;
 +              return 1;
 +
 +      argv_array_pushl(&cp.args, "status", "--porcelain",
 +                                 "--ignore-submodules=none", NULL);
 +
 +      if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED)
 +              argv_array_push(&cp.args, "-uno");
 +      else
 +              argv_array_push(&cp.args, "-uall");
 +
 +      if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))
 +              argv_array_push(&cp.args, "--ignored");
  
 -      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 -uall --ignore-submodules=none' in submodule %s", path);
 +      if (start_command(&cp)) {
 +              if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
 +                      die(_("could not start 'git status in submodule '%s'"),
 +                              path);
 +              ret = -1;
 +              goto out;
 +      }
  
        len = strbuf_read(&buf, cp.out, 1024);
        if (len > 2)
 -              ok_to_remove = 0;
 +              ret = 1;
        close(cp.out);
  
 -      if (finish_command(&cp))
 -              die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path);
 -
 +      if (finish_command(&cp)) {
 +              if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
 +                      die(_("could not run 'git status in submodule '%s'"),
 +                              path);
 +              ret = -1;
 +      }
 +out:
        strbuf_release(&buf);
 -      return ok_to_remove;
 +      return ret;
 +}
 +
 +static const char *get_super_prefix_or_empty(void)
 +{
 +      const char *s = get_super_prefix();
 +      if (!s)
 +              s = "";
 +      return s;
 +}
 +
 +static int submodule_has_dirty_index(const struct submodule *sub)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +
 +      cp.git_cmd = 1;
 +      argv_array_pushl(&cp.args, "diff-index", "--quiet",
 +                                 "--cached", "HEAD", NULL);
 +      cp.no_stdin = 1;
 +      cp.no_stdout = 1;
 +      cp.dir = sub->path;
 +      if (start_command(&cp))
 +              die("could not recurse into submodule '%s'", sub->path);
 +
 +      return finish_command(&cp);
 +}
 +
 +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);
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      cp.dir = path;
 +
 +      argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
 +                                 get_super_prefix_or_empty(), path);
 +      argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
 +
 +      argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
 +
 +      if (run_command(&cp))
 +              die("could not reset submodule index");
 +}
 +
 +/**
 + * Moves a submodule at a given path from a given head to another new head.
 + * For edge cases (a submodule coming into existence or removing a submodule)
 + * pass NULL for old or new respectively.
 + */
 +int submodule_move_head(const char *path,
 +                       const char *old,
 +                       const char *new,
 +                       unsigned flags)
 +{
 +      int ret = 0;
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      const struct submodule *sub;
 +
 +      sub = submodule_from_path(null_sha1, path);
 +
 +      if (!sub)
 +              die("BUG: could not get submodule information for '%s'", path);
 +
 +      if (old && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) {
 +              /* Check if the submodule has a dirty index. */
 +              if (submodule_has_dirty_index(sub))
 +                      return error(_("submodule '%s' has dirty index"), path);
 +      }
 +
 +      if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
 +              if (old) {
 +                      if (!submodule_uses_gitfile(path))
 +                              absorb_git_dir_into_superproject("", path,
 +                                      ABSORB_GITDIR_RECURSE_SUBMODULES);
 +              } else {
 +                      struct strbuf sb = STRBUF_INIT;
 +                      strbuf_addf(&sb, "%s/modules/%s",
 +                                  get_git_common_dir(), sub->name);
 +                      connect_work_tree_and_git_dir(path, sb.buf);
 +                      strbuf_release(&sb);
 +
 +                      /* make sure the index is clean as well */
 +                      submodule_reset_index(path);
 +              }
 +      }
 +
 +      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      cp.dir = path;
 +
 +      argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
 +                      get_super_prefix_or_empty(), path);
 +      argv_array_pushl(&cp.args, "read-tree", NULL);
 +
 +      if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN)
 +              argv_array_push(&cp.args, "-n");
 +      else
 +              argv_array_push(&cp.args, "-u");
 +
 +      if (flags & SUBMODULE_MOVE_HEAD_FORCE)
 +              argv_array_push(&cp.args, "--reset");
 +      else
 +              argv_array_push(&cp.args, "-m");
 +
 +      argv_array_push(&cp.args, old ? old : EMPTY_TREE_SHA1_HEX);
 +      argv_array_push(&cp.args, new ? new : EMPTY_TREE_SHA1_HEX);
 +
 +      if (run_command(&cp)) {
 +              ret = -1;
 +              goto out;
 +      }
 +
 +      if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
 +              if (new) {
 +                      struct child_process cp1 = CHILD_PROCESS_INIT;
 +                      /* also set the HEAD accordingly */
 +                      cp1.git_cmd = 1;
 +                      cp1.no_stdin = 1;
 +                      cp1.dir = path;
 +
 +                      argv_array_pushl(&cp1.args, "update-ref", "HEAD",
 +                                       new ? new : EMPTY_TREE_SHA1_HEX, NULL);
 +
 +                      if (run_command(&cp1)) {
 +                              ret = -1;
 +                              goto out;
 +                      }
 +              } else {
 +                      struct strbuf sb = STRBUF_INIT;
 +
 +                      strbuf_addf(&sb, "%s/.git", path);
 +                      unlink_or_warn(sb.buf);
 +                      strbuf_release(&sb);
 +
 +                      if (is_empty_dir(path))
 +                              rmdir_or_warn(path);
 +              }
 +      }
 +out:
 +      return ret;
  }
  
  static int find_first_merges(struct object_array *result, const char *path,
        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;
@@@ -1581,210 -1297,42 +1582,210 @@@ 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)
 +int parallel_submodules(void)
  {
 -      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);
 +      return parallel_jobs;
  }
  
 -int parallel_submodules(void)
 +/*
 + * 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)
  {
 -      return parallel_jobs;
 +      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 = real_pathdup(old_git_dir, 1);
 +
 +      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 = real_pathdup(new_git_dir, 1);
 +
 +      fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
 +              get_super_prefix_or_empty(), 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);
  }
  
 -void prepare_submodule_repo_env(struct argv_array *out)
 +/*
 + * 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 * const *var;
 +      int err_code;
 +      const char *sub_git_dir;
 +      struct strbuf gitdir = STRBUF_INIT;
 +      strbuf_addf(&gitdir, "%s/.git", path);
 +      sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code);
 +
 +      /* Not populated? */
 +      if (!sub_git_dir) {
 +              const struct submodule *sub;
 +
 +              if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
 +                      /* unpopulated as expected */
 +                      strbuf_release(&gitdir);
 +                      return;
 +              }
  
 -      for (var = local_repo_env; *var; var++) {
 -              if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
 -                      argv_array_push(out, *var);
 +              if (err_code != READ_GITFILE_ERR_NOT_A_REPO)
 +                      /* We don't know what broke here. */
 +                      read_gitfile_error_die(err_code, path, NULL);
 +
 +              /*
 +              * Maybe populated, but no git directory was found?
 +              * This can happen if the superproject is a submodule
 +              * itself and was just absorbed. The absorption of the
 +              * superproject did not rewrite the git file links yet,
 +              * fix it now.
 +              */
 +              sub = submodule_from_path(null_sha1, path);
 +              if (!sub)
 +                      die(_("could not lookup name for submodule '%s'"), path);
 +              connect_work_tree_and_git_dir(path,
 +                      git_path("modules/%s", sub->name));
 +      } else {
 +              /* Is it already absorbed into the superprojects git dir? */
 +              char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
 +              char *real_common_git_dir = real_pathdup(get_git_common_dir(), 1);
 +
 +              if (!starts_with(real_sub_git_dir, real_common_git_dir))
 +                      relocate_single_git_dir_into_superproject(prefix, path);
 +
 +              free(real_sub_git_dir);
 +              free(real_common_git_dir);
        }
 -      argv_array_push(out, "GIT_DIR=.git");
 +      strbuf_release(&gitdir);
 +
 +      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?");
 +
 +              strbuf_addstr(&sb, get_super_prefix_or_empty());
 +              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);
 +      }
 +}
 +
 +const char *get_superproject_working_tree(void)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *one_up = real_path_if_valid("../");
 +      const char *cwd = xgetcwd();
 +      const char *ret = NULL;
 +      const char *subpath;
 +      int code;
 +      ssize_t len;
 +
 +      if (!is_inside_work_tree())
 +              /*
 +               * FIXME:
 +               * We might have a superproject, but it is harder
 +               * to determine.
 +               */
 +              return NULL;
 +
 +      if (!one_up)
 +              return NULL;
 +
 +      subpath = relative_path(cwd, one_up, &sb);
 +
 +      prepare_submodule_repo_env(&cp.env_array);
 +      argv_array_pop(&cp.env_array);
 +
 +      argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..",
 +                      "ls-files", "-z", "--stage", "--full-name", "--",
 +                      subpath, NULL);
 +      strbuf_reset(&sb);
 +
 +      cp.no_stdin = 1;
 +      cp.no_stderr = 1;
 +      cp.out = -1;
 +      cp.git_cmd = 1;
 +
 +      if (start_command(&cp))
 +              die(_("could not start ls-files in .."));
 +
 +      len = strbuf_read(&sb, cp.out, PATH_MAX);
 +      close(cp.out);
 +
 +      if (starts_with(sb.buf, "160000")) {
 +              int super_sub_len;
 +              int cwd_len = strlen(cwd);
 +              char *super_sub, *super_wt;
 +
 +              /*
 +               * There is a superproject having this repo as a submodule.
 +               * The format is <mode> SP <hash> SP <stage> TAB <full name> \0,
 +               * We're only interested in the name after the tab.
 +               */
 +              super_sub = strchr(sb.buf, '\t') + 1;
 +              super_sub_len = sb.buf + sb.len - super_sub - 1;
 +
 +              if (super_sub_len > cwd_len ||
 +                  strcmp(&cwd[cwd_len - super_sub_len], super_sub))
 +                      die (_("BUG: returned path string doesn't match cwd?"));
 +
 +              super_wt = xstrdup(cwd);
 +              super_wt[cwd_len - super_sub_len] = '\0';
 +
 +              ret = real_path(super_wt);
 +              free(super_wt);
 +      }
 +      strbuf_release(&sb);
 +
 +      code = finish_command(&cp);
 +
 +      if (code == 128)
 +              /* '../' is not a git repository */
 +              return NULL;
 +      if (code == 0 && len == 0)
 +              /* There is an unrelated git repository at '../' */
 +              return NULL;
 +      if (code)
 +              die(_("ls-tree returned unexpected return code %d"), code);
 +
 +      return ret;
  }