Merge branch 'ss/submodule-summary-in-c'

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
This commit is contained in:
Junio C Hamano
2020-09-09 13:53:05 -07:00
7 changed files with 512 additions and 195 deletions

View File

@ -612,7 +612,6 @@ 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,
@ -742,7 +741,6 @@ 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,
@ -929,11 +927,438 @@ 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;
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;
};
#define SYNC_CB_INIT { NULL, 0 }
static void sync_submodule(const char *path, const char *prefix,
@ -2344,6 +2769,7 @@ static struct cmd_struct commands[] = {
{"print-default-remote", print_default_remote, 0},
{"sync", module_sync, SUPPORT_SUPER_PREFIX},
{"deinit", module_deinit, 0},
{"summary", module_summary, SUPPORT_SUPER_PREFIX},
{"remote-branch", resolve_remote_submodule_branch, 0},
{"push-check", push_check, 0},
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},