Merge branch 'pc/submodule-helper'
authorJunio C Hamano <gitster@pobox.com>
Mon, 6 Nov 2017 05:24:23 +0000 (14:24 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 6 Nov 2017 05:24:23 +0000 (14:24 +0900)
GSoC.

* pc/submodule-helper:
submodule: port submodule subcommand 'status' from shell to C
submodule--helper: introduce for_each_listed_submodule()
submodule--helper: introduce get_submodule_displaypath()

1  2 
builtin/submodule--helper.c
index 06ed02f99402be6a7edb36ff0eb08b903c513c6c,a4193c01d96629c66c62232e80d3d6be151aaab6..d366e8e7b369bfc500cfc8a06db0638a22b43ee0
  #include "remote.h"
  #include "refs.h"
  #include "connect.h"
+ #include "revision.h"
+ #include "diffcore.h"
+ #include "diff.h"
+ #define OPT_QUIET (1 << 0)
+ #define OPT_CACHED (1 << 1)
+ #define OPT_RECURSIVE (1 << 2)
+ typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
+                                 void *cb_data);
  
  static char *get_default_remote(void)
  {
        char *dest = NULL, *ret;
 -      unsigned char sha1[20];
        struct strbuf sb = STRBUF_INIT;
 -      const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
 +      const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
  
        if (!refname)
                die(_("No such ref: %s"), "HEAD");
@@@ -219,6 -230,64 +229,64 @@@ static int resolve_relative_url_test(in
        return 0;
  }
  
+ /* the result should be freed by the caller. */
+ static char *get_submodule_displaypath(const char *path, const char *prefix)
+ {
+       const char *super_prefix = get_super_prefix();
+       if (prefix && super_prefix) {
+               BUG("cannot have prefix '%s' and superprefix '%s'",
+                   prefix, super_prefix);
+       } else if (prefix) {
+               struct strbuf sb = STRBUF_INIT;
+               char *displaypath = xstrdup(relative_path(path, prefix, &sb));
+               strbuf_release(&sb);
+               return displaypath;
+       } else if (super_prefix) {
+               return xstrfmt("%s%s", super_prefix, path);
+       } else {
+               return xstrdup(path);
+       }
+ }
+ static char *compute_rev_name(const char *sub_path, const char* object_id)
+ {
+       struct strbuf sb = STRBUF_INIT;
+       const char ***d;
+       static const char *describe_bare[] = { NULL };
+       static const char *describe_tags[] = { "--tags", NULL };
+       static const char *describe_contains[] = { "--contains", NULL };
+       static const char *describe_all_always[] = { "--all", "--always", NULL };
+       static const char **describe_argv[] = { describe_bare, describe_tags,
+                                               describe_contains,
+                                               describe_all_always, NULL };
+       for (d = describe_argv; *d; d++) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+               prepare_submodule_repo_env(&cp.env_array);
+               cp.dir = sub_path;
+               cp.git_cmd = 1;
+               cp.no_stderr = 1;
+               argv_array_push(&cp.args, "describe");
+               argv_array_pushv(&cp.args, *d);
+               argv_array_push(&cp.args, object_id);
+               if (!capture_command(&cp, &sb, 0)) {
+                       strbuf_strip_suffix(&sb, "\n");
+                       return strbuf_detach(&sb, NULL);
+               }
+       }
+       strbuf_release(&sb);
+       return NULL;
+ }
  struct module_list {
        const struct cache_entry **entries;
        int alloc, nr;
@@@ -328,21 -397,29 +396,29 @@@ static int module_list(int argc, const 
        return 0;
  }
  
- static void init_submodule(const char *path, const char *prefix, int quiet)
+ static void for_each_listed_submodule(const struct module_list *list,
+                                     each_submodule_fn fn, void *cb_data)
+ {
+       int i;
+       for (i = 0; i < list->nr; i++)
+               fn(list->entries[i], cb_data);
+ }
+ struct init_cb {
+       const char *prefix;
+       unsigned int flags;
+ };
+ #define INIT_CB_INIT { NULL, 0 }
+ static void init_submodule(const char *path, const char *prefix,
+                          unsigned int flags)
  {
        const struct submodule *sub;
        struct strbuf sb = STRBUF_INIT;
        char *upd = NULL, *url = NULL, *displaypath;
  
-       if (prefix && get_super_prefix())
-               die("BUG: cannot have prefix and superprefix");
-       else if (prefix)
-               displaypath = xstrdup(relative_path(path, prefix, &sb));
-       else if (get_super_prefix()) {
-               strbuf_addf(&sb, "%s%s", get_super_prefix(), path);
-               displaypath = strbuf_detach(&sb, NULL);
-       } else
-               displaypath = xstrdup(path);
+       displaypath = get_submodule_displaypath(path, prefix);
  
        sub = submodule_from_path(&null_oid, path);
  
         * Set active flag for the submodule being initialized
         */
        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");
+               strbuf_reset(&sb);
        }
  
        /*
         * To look up the url in .git/config, we must not fall back to
         * .gitmodules, so look it up directly.
         */
-       strbuf_reset(&sb);
        strbuf_addf(&sb, "submodule.%s.url", sub->name);
        if (git_config_get_string(sb.buf, &url)) {
                if (!sub->url)
                if (git_config_set_gently(sb.buf, url))
                        die(_("Failed to register url for submodule path '%s'"),
                            displaypath);
-               if (!quiet)
+               if (!(flags & OPT_QUIET))
                        fprintf(stderr,
                                _("Submodule '%s' (%s) registered for path '%s'\n"),
                                sub->name, url, displaypath);
        }
+       strbuf_reset(&sb);
  
        /* Copy "update" setting when it is not set yet */
-       strbuf_reset(&sb);
        strbuf_addf(&sb, "submodule.%s.update", sub->name);
        if (git_config_get_string(sb.buf, &upd) &&
            sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
        free(upd);
  }
  
+ static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
+ {
+       struct init_cb *info = cb_data;
+       init_submodule(list_item->name, info->prefix, info->flags);
+ }
  static int module_init(int argc, const char **argv, const char *prefix)
  {
+       struct init_cb info = INIT_CB_INIT;
        struct pathspec pathspec;
        struct module_list list = MODULE_LIST_INIT;
        int quiet = 0;
-       int i;
  
        struct option module_init_options[] = {
                OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
        if (!argc && git_config_get_value_multi("submodule.active"))
                module_list_active(&list);
  
-       for (i = 0; i < list.nr; i++)
-               init_submodule(list.entries[i]->name, prefix, quiet);
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+       for_each_listed_submodule(&list, init_submodule_cb, &info);
+       return 0;
+ }
+ struct status_cb {
+       const char *prefix;
+       unsigned int flags;
+ };
+ #define STATUS_CB_INIT { NULL, 0 }
+ static void print_status(unsigned int flags, char state, const char *path,
+                        const struct object_id *oid, const char *displaypath)
+ {
+       if (flags & OPT_QUIET)
+               return;
+       printf("%c%s %s", state, oid_to_hex(oid), displaypath);
+       if (state == ' ' || state == '+')
+               printf(" (%s)", compute_rev_name(path, oid_to_hex(oid)));
+       printf("\n");
+ }
+ static int handle_submodule_head_ref(const char *refname,
+                                    const struct object_id *oid, int flags,
+                                    void *cb_data)
+ {
+       struct object_id *output = cb_data;
+       if (oid)
+               oidcpy(output, oid);
+       return 0;
+ }
+ static void status_submodule(const char *path, const struct object_id *ce_oid,
+                            unsigned int ce_flags, const char *prefix,
+                            unsigned int flags)
+ {
+       char *displaypath;
+       struct argv_array diff_files_args = ARGV_ARRAY_INIT;
+       struct rev_info rev;
+       int diff_files_result;
+       if (!submodule_from_path(&null_oid, path))
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                     path);
+       displaypath = get_submodule_displaypath(path, prefix);
+       if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) {
+               print_status(flags, 'U', path, &null_oid, displaypath);
+               goto cleanup;
+       }
+       if (!is_submodule_active(the_repository, path)) {
+               print_status(flags, '-', path, ce_oid, displaypath);
+               goto cleanup;
+       }
+       argv_array_pushl(&diff_files_args, "diff-files",
+                        "--ignore-submodules=dirty", "--quiet", "--",
+                        path, NULL);
+       git_config(git_diff_basic_config, NULL);
+       init_revisions(&rev, prefix);
+       rev.abbrev = 0;
+       diff_files_args.argc = setup_revisions(diff_files_args.argc,
+                                              diff_files_args.argv,
+                                              &rev, NULL);
+       diff_files_result = run_diff_files(&rev, 0);
+       if (!diff_result_code(&rev.diffopt, diff_files_result)) {
+               print_status(flags, ' ', path, ce_oid,
+                            displaypath);
+       } else if (!(flags & OPT_CACHED)) {
+               struct object_id oid;
+               if (refs_head_ref(get_submodule_ref_store(path),
+                                 handle_submodule_head_ref, &oid))
+                       die(_("could not resolve HEAD ref inside the"
+                             "submodule '%s'"), path);
+               print_status(flags, '+', path, &oid, displaypath);
+       } else {
+               print_status(flags, '+', path, ce_oid, displaypath);
+       }
+       if (flags & OPT_RECURSIVE) {
+               struct child_process cpr = CHILD_PROCESS_INIT;
+               cpr.git_cmd = 1;
+               cpr.dir = path;
+               prepare_submodule_repo_env(&cpr.env_array);
+               argv_array_push(&cpr.args, "--super-prefix");
+               argv_array_pushf(&cpr.args, "%s/", displaypath);
+               argv_array_pushl(&cpr.args, "submodule--helper", "status",
+                                "--recursive", NULL);
+               if (flags & OPT_CACHED)
+                       argv_array_push(&cpr.args, "--cached");
+               if (flags & OPT_QUIET)
+                       argv_array_push(&cpr.args, "--quiet");
+               if (run_command(&cpr))
+                       die(_("failed to recurse into submodule '%s'"), path);
+       }
+ cleanup:
+       argv_array_clear(&diff_files_args);
+       free(displaypath);
+ }
+ static void status_submodule_cb(const struct cache_entry *list_item,
+                               void *cb_data)
+ {
+       struct status_cb *info = cb_data;
+       status_submodule(list_item->name, &list_item->oid, list_item->ce_flags,
+                        info->prefix, info->flags);
+ }
+ static int module_status(int argc, const char **argv, const char *prefix)
+ {
+       struct status_cb info = STATUS_CB_INIT;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       int quiet = 0;
+       struct option module_status_options[] = {
+               OPT__QUIET(&quiet, N_("Suppress submodule status output")),
+               OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
+               OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE),
+               OPT_END()
+       };
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule status [--quiet] [--cached] [--recursive] [<path>...]"),
+               NULL
+       };
+       argc = parse_options(argc, argv, prefix, module_status_options,
+                            git_submodule_helper_usage, 0);
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+               return 1;
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+       for_each_listed_submodule(&list, status_submodule_cb, &info);
  
        return 0;
  }
@@@ -1088,7 -1327,8 +1326,7 @@@ static const char *remote_submodule_bra
                return "master";
  
        if (!strcmp(branch, ".")) {
 -              unsigned char sha1[20];
 -              const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
 +              const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
  
                if (!refname)
                        die(_("No such ref: %s"), "HEAD");
@@@ -1187,7 -1427,6 +1425,7 @@@ static int push_check(int argc, const c
                                                break;
                                        die("HEAD does not match the named branch in the superproject");
                                }
 +                              /* fallthrough */
                        default:
                                die("src refspec '%s' must name a ref",
                                    rs->src);
@@@ -1259,6 -1498,7 +1497,7 @@@ static struct cmd_struct commands[] = 
        {"resolve-relative-url", resolve_relative_url, 0},
        {"resolve-relative-url-test", resolve_relative_url_test, 0},
        {"init", module_init, SUPPORT_SUPER_PREFIX},
+       {"status", module_status, SUPPORT_SUPER_PREFIX},
        {"remote-branch", resolve_remote_submodule_branch, 0},
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},