@@ -767,6 +770,468 @@ static int module_name(int argc, const char **argv, const char *prefix)
return 0;
}
+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;
+ char *diff_cmd;
+ unsigned int cached: 1;
+ unsigned int for_status: 1;
+ unsigned int quiet: 1;
+ unsigned int files: 1;
+ int summary_limits;
+};
+#define SUMMARY_CB_INIT { 0, NULL, NULL, NULL, 0, 0, 0, 0, 0 }
+
+static void print_submodule_summary(struct summary_cb *info,
+ struct module_cb *p)
+{
+ int missing_src = 0;
+ int missing_dst = 0;
+ char *displaypath;
+ char *sha1_abbr_src;
+ char *sha1_abbr_dst;
+ int errmsg = 0;
+ int total_commits = -1;
+ struct strbuf sb_sha1_src = STRBUF_INIT;
+ struct strbuf sb_sha1_dst = STRBUF_INIT;
+ char *sha1_dst = oid_to_hex(&p->oid_dst);
+ char *sha1_src = oid_to_hex(&p->oid_src);
+ char *sm_git_dir = xstrfmt("%s/.git", p->sm_path);
+ int is_sm_git_dir = 0;
+
+ if (!info->cached && !strcmp(sha1_dst, sha1_to_hex(null_sha1))) {
+ if (S_ISGITLINK(p->mod_dst)) {
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+ struct strbuf sb_rev_parse = STRBUF_INIT;
+
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.no_stderr = 1;
+
+ argv_array_pushf(&cp_rev_parse.env_array,
+ "GIT_DIR=%s/.git", p->sm_path);
+ argv_array_pushl(&cp_rev_parse.args,
+ "rev-parse", "HEAD", NULL);
+ if (!capture_command(&cp_rev_parse, &sb_rev_parse, 0)) {
+ strbuf_strip_suffix(&sb_rev_parse, "\n");
+ sha1_dst = xstrdup(sb_rev_parse.buf);
+ }
+ strbuf_release(&sb_rev_parse);
+ } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) {
+ struct child_process cp_hash_object = CHILD_PROCESS_INIT;
+ struct strbuf sb_hash_object = STRBUF_INIT;
+
+ cp_hash_object.git_cmd = 1;
+ argv_array_pushl(&cp_hash_object.args,
+ "hash-object", p->sm_path,
+ NULL);
+ if (!capture_command(&cp_hash_object,
+ &sb_hash_object, 0)) {
+ strbuf_strip_suffix(&sb_hash_object, "\n");
+ sha1_dst = xstrdup(sb_hash_object.buf);
+ }
+ strbuf_release(&sb_hash_object);
+ } else {
+ if (p->mod_dst != 0)
+ die(_("unexpected mode %d\n"), p->mod_dst);
+ }
+ }
+
+ if (is_git_directory(sm_git_dir))
+ is_sm_git_dir = 1;
+
+ if (is_sm_git_dir && S_ISGITLINK(p->mod_src)) {
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.no_stdout = 1;
+
+ argv_array_pushf(&cp_rev_parse.env_array, "GIT_DIR=%s/.git",
+ p->sm_path);
+ argv_array_pushl(&cp_rev_parse.args, "rev-parse", "-q",
+ "--verify", NULL);
+ argv_array_pushf(&cp_rev_parse.args, "%s^0", sha1_src);
+
+ if (run_command(&cp_rev_parse))
+ missing_src = 1;
+ }
+
+ if (is_sm_git_dir && S_ISGITLINK(p->mod_dst)) {
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.no_stdout = 1;
+
+ argv_array_pushf(&cp_rev_parse.env_array, "GIT_DIR=%s/.git",
+ p->sm_path);
+ argv_array_pushl(&cp_rev_parse.args, "rev-parse", "-q",
+ "--verify", NULL);
+ argv_array_pushf(&cp_rev_parse.args, "%s^0", sha1_dst);
+
+ if (run_command(&cp_rev_parse))
+ missing_dst = 1;
+ }
+
+ displaypath = get_submodule_displaypath(p->sm_path, info->prefix);
+
+ if (!missing_dst && !missing_src) {
+ if (is_sm_git_dir) {
+ struct child_process cp_rev_list = CHILD_PROCESS_INIT;
+ struct strbuf sb_rev_list = STRBUF_INIT;
+ const char *range;
+
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst))
+ range = xstrfmt("%s...%s", sha1_src, sha1_dst);
+ else if (S_ISGITLINK(p->mod_src))
+ range = xstrdup(sha1_src);
+ else
+ range = xstrdup(sha1_dst);
+
+ cp_rev_list.git_cmd = 1;
+ argv_array_pushf(&cp_rev_list.env_array,
+ "GIT_DIR=%s/.git", p->sm_path);
+ argv_array_pushl(&cp_rev_list.args, "rev-list",
+ "--first-parent", range, "--", NULL);
+ if (!capture_command(&cp_rev_list, &sb_rev_list, 0)) {
+ if (sb_rev_list.len)
+ total_commits = count_lines(sb_rev_list.buf,
+ sb_rev_list.len);
+ else
+ total_commits = 0;
+ }
+ strbuf_release(&sb_rev_list);
+ }
+ } else {
+ errmsg = 1;
+ }
+
+ strbuf_addstr(&sb_sha1_src, sha1_src);
+ strbuf_addstr(&sb_sha1_dst, sha1_dst);
+
+ strbuf_remove(&sb_sha1_src, 7, 33);
+ strbuf_remove(&sb_sha1_dst, 7, 33);
+
+ sha1_abbr_src = strbuf_detach(&sb_sha1_src, NULL);
+ sha1_abbr_dst = strbuf_detach(&sb_sha1_dst, NULL);
+
+ if (p->status == 'T') {
+ if (S_ISGITLINK(p->mod_dst)) {
+ if (total_commits < 0)
+ printf(_("* %s %s(blob)->%s(submodule):\n"),
+ displaypath, sha1_abbr_src,
+ sha1_abbr_dst);
+ else
+ printf(_("* %s %s(blob)->%s(submodule) (%d):\n"),
+ displaypath, sha1_abbr_src,
+ sha1_abbr_dst, total_commits);
+ } else {
+ if (total_commits < 0)
+ printf(_("* %s %s(submodule)->%s(blob):\n"),
+ displaypath, sha1_abbr_src,
+ sha1_abbr_dst);
+ else
+ printf(_("* %s %s(submodule)->%s(blob) (%d):\n"),
+ displaypath, sha1_abbr_src,
+ sha1_abbr_dst, total_commits);
+ }
+ } else {
+ if (total_commits < 0)
+ printf("* %s %s...%s:\n", displaypath, sha1_abbr_src,
+ sha1_abbr_dst);
+ else
+ printf("* %s %s...%s (%d):\n", displaypath,
+ sha1_abbr_src, sha1_abbr_dst, total_commits);
+ }
+
+ if (errmsg) {
+ /*
+ * Don't give error msg for modification whose dst is not
+ * submodule, i.e. deleted or changed to blob
+ */
+ if (S_ISGITLINK(p->mod_src)) {
+ if (missing_src && missing_dst) {
+ printf(_(" Warn: %s doesn't contain commits %s and %s\n"),
+ displaypath, sha1_src, sha1_dst);
+ } else if (missing_src) {
+ printf(_(" Warn: %s doesn't contain commit %s\n"),
+ displaypath, sha1_src);
+ } else {
+ printf(_(" Warn: %s doesn't contain commit %s\n"),
+ displaypath, sha1_dst);
+ }
+ }
+
+ } else if (is_sm_git_dir) {
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) {
+ struct child_process cp_log = CHILD_PROCESS_INIT;
+ char *limit = NULL;
+
+ if (info->summary_limits > 0)
+ limit = xstrfmt("-%d", info->summary_limits);
+
+ cp_log.git_cmd = 1;
+
+ argv_array_pushf(&cp_log.env_array, "GIT_DIR=%s/.git",
+ p->sm_path);
+ argv_array_pushl(&cp_log.args, "log", NULL);
+ if (limit)
+ argv_array_push(&cp_log.args, limit);
+ argv_array_pushl(&cp_log.args, "--pretty= %m %s",
+ "--first-parent", NULL);
+ argv_array_pushf(&cp_log.args, "%s...%s", sha1_src,
+ sha1_dst);
+
+ run_command(&cp_log);
+
+ } else if (S_ISGITLINK(p->mod_dst)) {
+ struct child_process cp_log = CHILD_PROCESS_INIT;
+
+ cp_log.git_cmd = 1;
+ argv_array_pushf(&cp_log.env_array, "GIT_DIR=%s/.git",
+ p->sm_path);
+ argv_array_pushl(&cp_log.args, "log",
+ "--pretty= > %s", "-1",
+ sha1_dst, NULL);
+
+ run_command(&cp_log);
+ } else {
+ struct child_process cp_log = CHILD_PROCESS_INIT;
+
+ cp_log.git_cmd = 1;
+ argv_array_pushf(&cp_log.env_array, "GIT_DIR=%s/.git",
+ p->sm_path);
+ argv_array_pushl(&cp_log.args, "log",
+ "--pretty= < %s",
+ "-1", sha1_src, NULL);
+
+ run_command(&cp_log);
+ }
+ }
+ printf("\n");
+}
+
+static void prepare_submodule_summary(struct summary_cb *info,
+ struct module_cb_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ struct module_cb *p = list->entries[i];
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+
+ if (p->status == 'D' || p->status == 'T') {
+ print_submodule_summary(info, p);
+ continue;
+ }
+
+ if (info->for_status) {
+ char *config_key;
+ const char *ignore_config = "none";
+ const char *value;
+ const struct submodule *sub = submodule_from_path(null_sha1, p->sm_path);
+
+ if (sub) {
+ config_key = xstrfmt("submodule.%s.ignore",
+ sub->name);
+ if (!git_config_get_value(config_key, &value))
+ ignore_config = value;
+ else if (sub->ignore)
+ ignore_config = sub->ignore;
+
+ if (p->status != 'A' && !strcmp(ignore_config,
+ "all"))
+ continue;
+ }
+ }
+
+ /* Also show added or modified modules which are checked out */
+ cp_rev_parse.dir = p->sm_path;
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.no_stderr = 1;
+ cp_rev_parse.no_stdout = 1;
+
+ argv_array_pushl(&cp_rev_parse.args, "rev-parse",
+ "--git-dir", NULL);
+
+ if (!run_command(&cp_rev_parse))
+ print_submodule_summary(info, p);
+ }
+}
+
+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 int compute_summary_module_list(char *head, struct summary_cb *info)
+{
+ struct argv_array diff_args = ARGV_ARRAY_INIT;
+ struct rev_info rev;
+ struct module_cb_list list = MODULE_CB_LIST_INIT;
+
+ argv_array_push(&diff_args, info->diff_cmd);
+ if (info->cached)
+ argv_array_push(&diff_args, "--cached");
+ argv_array_pushl(&diff_args, "--ignore-submodules=dirty", "--raw",
+ NULL);
+ if (head)
+ argv_array_push(&diff_args, head);
+ argv_array_push(&diff_args, "--");
+ if (info->argc)
+ argv_array_pushv(&diff_args, info->argv);
+
+ git_config(git_diff_basic_config, NULL);
+ init_revisions(&rev, info->prefix);
+ gitmodules_config();
+ rev.abbrev = 0;
+ precompose_argv(diff_args.argc, diff_args.argv);
+
+ diff_args.argc = setup_revisions(diff_args.argc, diff_args.argv,
+ &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 (!strcmp(info->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 (!strcmp(info->diff_cmd, "diff-index"))
+ run_diff_index(&rev, info->cached);
+ else
+ run_diff_files(&rev, 0);
+ prepare_submodule_summary(info, &list);
+ return 0;
+
+}
+
+static int module_summary(int argc, const char **argv, const char *prefix)
+{
+ struct summary_cb info = SUMMARY_CB_INIT;
+ int cached = 0;
+ char *diff_cmd = "diff-index";
+ int for_status = 0;
+ int quiet = 0;
+ int files = 0;
+ int summary_limits = -1;
+ char *head = NULL;
+ struct child_process cp_rev = CHILD_PROCESS_INIT;
+ struct strbuf sb_rev = STRBUF_INIT;
+
+ struct option module_summary_options[] = {
+ OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
+ 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 compares the commit in the index with that in the submodule HEAD")),
+ OPT_BOOL(0, "for-status", &for_status, N_("Skip submodules with 'all' ignore_config value")),
+ OPT_INTEGER('n', "summary-limits", &summary_limits, N_("Limit the summary size")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper summary [<options>] [--] [<path>]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_summary_options,
+ git_submodule_helper_usage, 0);
+
+ if (!summary_limits)
+ return 0;
+
+ cp_rev.git_cmd = 1;
+ argv_array_pushl(&cp_rev.args, "rev-parse", "-q", "--verify",
+ NULL);
+ if (argc)
+ argv_array_push(&cp_rev.args, argv[0]);
+ else
+ argv_array_pushl(&cp_rev.args, "HEAD", NULL);
+
+ if (!capture_command(&cp_rev, &sb_rev, 0)) {
+ strbuf_strip_suffix(&sb_rev, "\n");
+ head = xstrdup(sb_rev.buf);
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ strbuf_release(&sb_rev);
+ } else if (!argc || !strcmp(argv[0], "HEAD")) {
+ /* before the first commit: compare with an empty tree */
+ struct stat st;
+ unsigned char sha1[20];
+ if (fstat(0, &st) < 0 || index_fd(sha1, 0, &st, 2, prefix, 3))
+ die("Unable to add %s to database", sha1);
+ head = sha1_to_hex(sha1);
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ } else {
+ head = "HEAD";
+ }
+
+ if (files) {
+ if (cached)
+ die(_("The --cached option cannot be used with the --files option"));
+ diff_cmd = "diff-files";
+ head = NULL;
+ }
+
+ info.argc = argc;
+ info.argv = argv;
+ info.prefix = prefix;
+ info.cached = cached;
+ info.for_status = for_status;
+ info.quiet = quiet;
+ info.files = files;
+ info.summary_limits = summary_limits;
+ info.diff_cmd = diff_cmd;
+
+ return compute_summary_module_list(head, &info);
+}
+
struct sync_cb {
const char *prefix;
unsigned int quiet: 1;