]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ss/submodule-summary-in-c'
authorJunio C Hamano <gitster@pobox.com>
Wed, 9 Sep 2020 20:53:05 +0000 (13:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 9 Sep 2020 20:53:05 +0000 (13:53 -0700)
Yet another subcommand of "git submodule" is getting rewritten in C.

* ss/submodule-summary-in-c:
  submodule: port submodule subcommand 'summary' from shell to C
  t7421: introduce a test script for verifying 'summary' output
  submodule: rename helper functions to avoid ambiguity
  submodule: remove extra line feeds between callback struct and macro

1  2 
builtin/submodule--helper.c
diff.c
submodule.c
t/t7401-submodule-summary.sh

index a59d8e4bda615cf843515da2312febdf2c72c213,63ea39025d037a7ca2a1c79087e3ff6a58a81f76..b9df79befc0773ca4821654011d83efe8ea8fd40
@@@ -929,6 -927,434 +927,434 @@@ static int module_name(int argc, const 
        return 0;
  }
  
 -                      if (!git_config_get_string_const(config_key, &value))
+ struct module_cb {
+       unsigned int mod_src;
+       unsigned int mod_dst;
+       struct object_id oid_src;
+       struct object_id oid_dst;
+       char status;
+       const char *sm_path;
+ };
+ #define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+ struct module_cb_list {
+       struct module_cb **entries;
+       int alloc, nr;
+ };
+ #define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+ struct summary_cb {
+       int argc;
+       const char **argv;
+       const char *prefix;
+       unsigned int cached: 1;
+       unsigned int for_status: 1;
+       unsigned int files: 1;
+       int summary_limit;
+ };
+ #define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+ enum diff_cmd {
+       DIFF_INDEX,
+       DIFF_FILES
+ };
+ static char* verify_submodule_committish(const char *sm_path,
+                                        const char *committish)
+ {
+       struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+       struct strbuf result = STRBUF_INIT;
+       cp_rev_parse.git_cmd = 1;
+       cp_rev_parse.dir = sm_path;
+       prepare_submodule_repo_env(&cp_rev_parse.env_array);
+       strvec_pushl(&cp_rev_parse.args, "rev-parse", "-q", "--short", NULL);
+       strvec_pushf(&cp_rev_parse.args, "%s^0", committish);
+       strvec_push(&cp_rev_parse.args, "--");
+       if (capture_command(&cp_rev_parse, &result, 0))
+               return NULL;
+       strbuf_trim_trailing_newline(&result);
+       return strbuf_detach(&result, NULL);
+ }
+ static void print_submodule_summary(struct summary_cb *info, char* errmsg,
+                                   int total_commits, const char *displaypath,
+                                   const char *src_abbrev, const char *dst_abbrev,
+                                   int missing_src, int missing_dst,
+                                   struct module_cb *p)
+ {
+       if (p->status == 'T') {
+               if (S_ISGITLINK(p->mod_dst))
+                       printf(_("* %s %s(blob)->%s(submodule)"),
+                                displaypath, src_abbrev, dst_abbrev);
+               else
+                       printf(_("* %s %s(submodule)->%s(blob)"),
+                                displaypath, src_abbrev, dst_abbrev);
+       } else {
+               printf("* %s %s...%s",
+                       displaypath, src_abbrev, dst_abbrev);
+       }
+       if (total_commits < 0)
+               printf(":\n");
+       else
+               printf(" (%d):\n", total_commits);
+       if (errmsg) {
+               printf(_("%s"), errmsg);
+       } else if (total_commits > 0) {
+               struct child_process cp_log = CHILD_PROCESS_INIT;
+               cp_log.git_cmd = 1;
+               cp_log.dir = p->sm_path;
+               prepare_submodule_repo_env(&cp_log.env_array);
+               strvec_pushl(&cp_log.args, "log", NULL);
+               if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) {
+                       if (info->summary_limit > 0)
+                               strvec_pushf(&cp_log.args, "-%d",
+                                            info->summary_limit);
+                       strvec_pushl(&cp_log.args, "--pretty=  %m %s",
+                                    "--first-parent", NULL);
+                       strvec_pushf(&cp_log.args, "%s...%s",
+                                    src_abbrev, dst_abbrev);
+               } else if (S_ISGITLINK(p->mod_dst)) {
+                       strvec_pushl(&cp_log.args, "--pretty=  > %s",
+                                    "-1", dst_abbrev, NULL);
+               } else {
+                       strvec_pushl(&cp_log.args, "--pretty=  < %s",
+                                    "-1", src_abbrev, NULL);
+               }
+               run_command(&cp_log);
+       }
+       printf("\n");
+ }
+ static void generate_submodule_summary(struct summary_cb *info,
+                                      struct module_cb *p)
+ {
+       char *displaypath, *src_abbrev, *dst_abbrev;
+       int missing_src = 0, missing_dst = 0;
+       char *errmsg = NULL;
+       int total_commits = -1;
+       if (!info->cached && oideq(&p->oid_dst, &null_oid)) {
+               if (S_ISGITLINK(p->mod_dst)) {
+                       struct ref_store *refs = get_submodule_ref_store(p->sm_path);
+                       if (refs)
+                               refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst);
+               } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) {
+                       struct stat st;
+                       int fd = open(p->sm_path, O_RDONLY);
+                       if (fd < 0 || fstat(fd, &st) < 0 ||
+                           index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB,
+                                    p->sm_path, 0))
+                               error(_("couldn't hash object from '%s'"), p->sm_path);
+               } else {
+                       /* for a submodule removal (mode:0000000), don't warn */
+                       if (p->mod_dst)
+                               warning(_("unexpected mode %d\n"), p->mod_dst);
+               }
+       }
+       if (S_ISGITLINK(p->mod_src)) {
+               src_abbrev = verify_submodule_committish(p->sm_path,
+                                                        oid_to_hex(&p->oid_src));
+               if (!src_abbrev) {
+                       missing_src = 1;
+                       /*
+                        * As `rev-parse` failed, we fallback to getting
+                        * the abbreviated hash using oid_src. We do
+                        * this as we might still need the abbreviated
+                        * hash in cases like a submodule type change, etc.
+                        */
+                       src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+               }
+       } else {
+               /*
+                * The source does not point to a submodule.
+                * So, we fallback to getting the abbreviation using
+                * oid_src as we might still need the abbreviated
+                * hash in cases like submodule add, etc.
+                */
+               src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+       }
+       if (S_ISGITLINK(p->mod_dst)) {
+               dst_abbrev = verify_submodule_committish(p->sm_path,
+                                                        oid_to_hex(&p->oid_dst));
+               if (!dst_abbrev) {
+                       missing_dst = 1;
+                       /*
+                        * As `rev-parse` failed, we fallback to getting
+                        * the abbreviated hash using oid_dst. We do
+                        * this as we might still need the abbreviated
+                        * hash in cases like a submodule type change, etc.
+                        */
+                       dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+               }
+       } else {
+               /*
+                * The destination does not point to a submodule.
+                * So, we fallback to getting the abbreviation using
+                * oid_dst as we might still need the abbreviated
+                * hash in cases like a submodule removal, etc.
+                */
+               dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+       }
+       displaypath = get_submodule_displaypath(p->sm_path, info->prefix);
+       if (!missing_src && !missing_dst) {
+               struct child_process cp_rev_list = CHILD_PROCESS_INIT;
+               struct strbuf sb_rev_list = STRBUF_INIT;
+               strvec_pushl(&cp_rev_list.args, "rev-list",
+                            "--first-parent", "--count", NULL);
+               if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst))
+                       strvec_pushf(&cp_rev_list.args, "%s...%s",
+                                    src_abbrev, dst_abbrev);
+               else
+                       strvec_push(&cp_rev_list.args, S_ISGITLINK(p->mod_src) ?
+                                   src_abbrev : dst_abbrev);
+               strvec_push(&cp_rev_list.args, "--");
+               cp_rev_list.git_cmd = 1;
+               cp_rev_list.dir = p->sm_path;
+               prepare_submodule_repo_env(&cp_rev_list.env_array);
+               if (!capture_command(&cp_rev_list, &sb_rev_list, 0))
+                       total_commits = atoi(sb_rev_list.buf);
+               strbuf_release(&sb_rev_list);
+       } else {
+               /*
+                * Don't give error msg for modification whose dst is not
+                * submodule, i.e., deleted or changed to blob
+                */
+               if (S_ISGITLINK(p->mod_dst)) {
+                       struct strbuf errmsg_str = STRBUF_INIT;
+                       if (missing_src && missing_dst) {
+                               strbuf_addf(&errmsg_str, "  Warn: %s doesn't contain commits %s and %s\n",
+                                           displaypath, oid_to_hex(&p->oid_src),
+                                           oid_to_hex(&p->oid_dst));
+                       } else {
+                               strbuf_addf(&errmsg_str, "  Warn: %s doesn't contain commit %s\n",
+                                           displaypath, missing_src ?
+                                           oid_to_hex(&p->oid_src) :
+                                           oid_to_hex(&p->oid_dst));
+                       }
+                       errmsg = strbuf_detach(&errmsg_str, NULL);
+               }
+       }
+       print_submodule_summary(info, errmsg, total_commits,
+                               displaypath, src_abbrev,
+                               dst_abbrev, missing_src,
+                               missing_dst, p);
+       free(displaypath);
+       free(src_abbrev);
+       free(dst_abbrev);
+ }
+ static void prepare_submodule_summary(struct summary_cb *info,
+                                     struct module_cb_list *list)
+ {
+       int i;
+       for (i = 0; i < list->nr; i++) {
+               const struct submodule *sub;
+               struct module_cb *p = list->entries[i];
+               struct strbuf sm_gitdir = STRBUF_INIT;
+               if (p->status == 'D' || p->status == 'T') {
+                       generate_submodule_summary(info, p);
+                       continue;
+               }
+               if (info->for_status && p->status != 'A' &&
+                   (sub = submodule_from_path(the_repository,
+                                              &null_oid, p->sm_path))) {
+                       char *config_key = NULL;
+                       const char *value;
+                       int ignore_all = 0;
+                       config_key = xstrfmt("submodule.%s.ignore",
+                                            sub->name);
++                      if (!git_config_get_string_tmp(config_key, &value))
+                               ignore_all = !strcmp(value, "all");
+                       else if (sub->ignore)
+                               ignore_all = !strcmp(sub->ignore, "all");
+                       free(config_key);
+                       if (ignore_all)
+                               continue;
+               }
+               /* Also show added or modified modules which are checked out */
+               strbuf_addstr(&sm_gitdir, p->sm_path);
+               if (is_nonbare_repository_dir(&sm_gitdir))
+                       generate_submodule_summary(info, p);
+               strbuf_release(&sm_gitdir);
+       }
+ }
+ static void submodule_summary_callback(struct diff_queue_struct *q,
+                                      struct diff_options *options,
+                                      void *data)
+ {
+       int i;
+       struct module_cb_list *list = data;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               struct module_cb *temp;
+               if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode))
+                       continue;
+               temp = (struct module_cb*)malloc(sizeof(struct module_cb));
+               temp->mod_src = p->one->mode;
+               temp->mod_dst = p->two->mode;
+               temp->oid_src = p->one->oid;
+               temp->oid_dst = p->two->oid;
+               temp->status = p->status;
+               temp->sm_path = xstrdup(p->one->path);
+               ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+               list->entries[list->nr++] = temp;
+       }
+ }
+ static const char *get_diff_cmd(enum diff_cmd diff_cmd)
+ {
+       switch (diff_cmd) {
+       case DIFF_INDEX: return "diff-index";
+       case DIFF_FILES: return "diff-files";
+       default: BUG("bad diff_cmd value %d", diff_cmd);
+       }
+ }
+ static int compute_summary_module_list(struct object_id *head_oid,
+                                      struct summary_cb *info,
+                                      enum diff_cmd diff_cmd)
+ {
+       struct strvec diff_args = STRVEC_INIT;
+       struct rev_info rev;
+       struct module_cb_list list = MODULE_CB_LIST_INIT;
+       strvec_push(&diff_args, get_diff_cmd(diff_cmd));
+       if (info->cached)
+               strvec_push(&diff_args, "--cached");
+       strvec_pushl(&diff_args, "--ignore-submodules=dirty", "--raw", NULL);
+       if (head_oid)
+               strvec_push(&diff_args, oid_to_hex(head_oid));
+       strvec_push(&diff_args, "--");
+       if (info->argc)
+               strvec_pushv(&diff_args, info->argv);
+       git_config(git_diff_basic_config, NULL);
+       init_revisions(&rev, info->prefix);
+       rev.abbrev = 0;
+       precompose_argv(diff_args.nr, diff_args.v);
+       setup_revisions(diff_args.nr, diff_args.v, &rev, NULL);
+       rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = submodule_summary_callback;
+       rev.diffopt.format_callback_data = &list;
+       if (!info->cached) {
+               if (diff_cmd == DIFF_INDEX)
+                       setup_work_tree();
+               if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+                       perror("read_cache_preload");
+                       return -1;
+               }
+       } else if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       if (diff_cmd == DIFF_INDEX)
+               run_diff_index(&rev, info->cached);
+       else
+               run_diff_files(&rev, 0);
+       prepare_submodule_summary(info, &list);
+       strvec_clear(&diff_args);
+       return 0;
+ }
+ static int module_summary(int argc, const char **argv, const char *prefix)
+ {
+       struct summary_cb info = SUMMARY_CB_INIT;
+       int cached = 0;
+       int for_status = 0;
+       int files = 0;
+       int summary_limit = -1;
+       enum diff_cmd diff_cmd = DIFF_INDEX;
+       struct object_id head_oid;
+       int ret;
+       struct option module_summary_options[] = {
+               OPT_BOOL(0, "cached", &cached,
+                        N_("use the commit stored in the index instead of the submodule HEAD")),
+               OPT_BOOL(0, "files", &files,
+                        N_("to compare the commit in the index with that in the submodule HEAD")),
+               OPT_BOOL(0, "for-status", &for_status,
+                        N_("skip submodules with 'ignore_config' value set to 'all'")),
+               OPT_INTEGER('n', "summary-limit", &summary_limit,
+                            N_("limit the summary size")),
+               OPT_END()
+       };
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper summary [<options>] [commit] [--] [<path>]"),
+               NULL
+       };
+       argc = parse_options(argc, argv, prefix, module_summary_options,
+                            git_submodule_helper_usage, 0);
+       if (!summary_limit)
+               return 0;
+       if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) {
+               if (argc) {
+                       argv++;
+                       argc--;
+               }
+       } else if (!argc || !strcmp(argv[0], "HEAD")) {
+               /* before the first commit: compare with an empty tree */
+               oidcpy(&head_oid, the_hash_algo->empty_tree);
+               if (argc) {
+                       argv++;
+                       argc--;
+               }
+       } else {
+               if (get_oid("HEAD", &head_oid))
+                       die(_("could not fetch a revision for HEAD"));
+       }
+       if (files) {
+               if (cached)
+                       die(_("--cached and --files are mutually exclusive"));
+               diff_cmd = DIFF_FILES;
+       }
+       info.argc = argc;
+       info.argv = argv;
+       info.prefix = prefix;
+       info.cached = !!cached;
+       info.files = !!files;
+       info.for_status = !!for_status;
+       info.summary_limit = summary_limit;
+       ret = compute_summary_module_list((diff_cmd == DIFF_INDEX) ? &head_oid : NULL,
+                                         &info, diff_cmd);
+       return ret;
+ }
  struct sync_cb {
        const char *prefix;
        unsigned int flags;
diff --cc diff.c
Simple merge
diff --cc submodule.c
Simple merge
index cc87d26619477dfc1c330ae3a0a6cf498e5bdad1,45c5d2424e341384d513cf387a05df43fc979e67..76088147089c9bacdf0cb9f7cb570c55c1ec3f4d
@@@ -5,11 -5,12 +5,17 @@@
  
  test_description='Summary support for submodules
  
 -This test tries to verify the sanity of summary subcommand of git submodule.
 +This test script tries to verify the sanity of summary subcommand of git submodule.
  '
++
+ # NOTE: This test script uses 'git add' instead of 'git submodule add' to add
+ # submodules to the superproject. Some submodule subcommands such as init and
+ # deinit might not work as expected in this script. t7421 does not have this
+ # caveat.
++#
 +# NEEDSWORK: This test script is old fashioned and may need a big cleanup due to
 +# various reasons, one of them being that there are lots of commands taking place
 +# outside of 'test_expect_success' block, which is no longer in good-style.
  
  . ./test-lib.sh