Merge branch 'bw/ls-files-recurse-submodules'

"git ls-files" learned "--recurse-submodules" option that can be
used to get a listing of tracked files across submodules (i.e. this
only works with "--cached" option, not for listing untracked or
ignored files).  This would be a useful tool to sit on the upstream
side of a pipe that is read with xargs to work on all working tree
files from the top-level superproject.

* bw/ls-files-recurse-submodules:
  ls-files: add pathspec matching for submodules
  ls-files: pass through safe options for --recurse-submodules
  ls-files: optionally recurse into submodules
  git: make super-prefix option
This commit is contained in:
Junio C Hamano
2016-10-26 13:14:44 -07:00
9 changed files with 453 additions and 45 deletions

View File

@ -14,6 +14,7 @@
#include "resolve-undo.h"
#include "string-list.h"
#include "pathspec.h"
#include "run-command.h"
static int abbrev;
static int show_deleted;
@ -28,8 +29,11 @@ static int show_valid_bit;
static int line_terminator = '\n';
static int debug_mode;
static int show_eol;
static int recurse_submodules;
static struct argv_array submodules_options = ARGV_ARRAY_INIT;
static const char *prefix;
static const char *super_prefix;
static int max_prefix_len;
static int prefix_len;
static struct pathspec pathspec;
@ -67,12 +71,25 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path)
static void write_name(const char *name)
{
/*
* Prepend the super_prefix to name to construct the full_name to be
* written.
*/
struct strbuf full_name = STRBUF_INIT;
if (super_prefix) {
strbuf_addstr(&full_name, super_prefix);
strbuf_addstr(&full_name, name);
name = full_name.buf;
}
/*
* With "--full-name", prefix_len=0; this caller needs to pass
* an empty string in that case (a NULL is good for "").
*/
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
stdout, line_terminator);
strbuf_release(&full_name);
}
static void show_dir_entry(const char *tag, struct dir_entry *ent)
@ -152,55 +169,117 @@ static void show_killed_files(struct dir_struct *dir)
}
}
/*
* Compile an argv_array with all of the options supported by --recurse_submodules
*/
static void compile_submodule_options(const struct dir_struct *dir, int show_tag)
{
if (line_terminator == '\0')
argv_array_push(&submodules_options, "-z");
if (show_tag)
argv_array_push(&submodules_options, "-t");
if (show_valid_bit)
argv_array_push(&submodules_options, "-v");
if (show_cached)
argv_array_push(&submodules_options, "--cached");
if (show_eol)
argv_array_push(&submodules_options, "--eol");
if (debug_mode)
argv_array_push(&submodules_options, "--debug");
}
/**
* Recursively call ls-files on a submodule
*/
static void show_gitlink(const struct cache_entry *ce)
{
struct child_process cp = CHILD_PROCESS_INIT;
int status;
int i;
argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
super_prefix ? super_prefix : "",
ce->name);
argv_array_push(&cp.args, "ls-files");
argv_array_push(&cp.args, "--recurse-submodules");
/* add supported options */
argv_array_pushv(&cp.args, submodules_options.argv);
/*
* Pass in the original pathspec args. The submodule will be
* responsible for prepending the 'submodule_prefix' prior to comparing
* against the pathspec for matches.
*/
argv_array_push(&cp.args, "--");
for (i = 0; i < pathspec.nr; i++)
argv_array_push(&cp.args, pathspec.items[i].original);
cp.git_cmd = 1;
cp.dir = ce->name;
status = run_command(&cp);
if (status)
exit(status);
}
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
{
struct strbuf name = STRBUF_INIT;
int len = max_prefix_len;
if (super_prefix)
strbuf_addstr(&name, super_prefix);
strbuf_addstr(&name, ce->name);
if (len >= ce_namelen(ce))
die("git ls-files: internal error - cache entry not superset of prefix");
if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce),
len, ps_matched,
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
return;
if (tag && *tag && show_valid_bit &&
(ce->ce_flags & CE_VALID)) {
static char alttag[4];
memcpy(alttag, tag, 3);
if (isalpha(tag[0]))
alttag[0] = tolower(tag[0]);
else if (tag[0] == '?')
alttag[0] = '!';
else {
alttag[0] = 'v';
alttag[1] = tag[0];
alttag[2] = ' ';
alttag[3] = 0;
if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
submodule_path_match(&pathspec, name.buf, ps_matched)) {
show_gitlink(ce);
} else if (match_pathspec(&pathspec, name.buf, name.len,
len, ps_matched,
S_ISDIR(ce->ce_mode) ||
S_ISGITLINK(ce->ce_mode))) {
if (tag && *tag && show_valid_bit &&
(ce->ce_flags & CE_VALID)) {
static char alttag[4];
memcpy(alttag, tag, 3);
if (isalpha(tag[0]))
alttag[0] = tolower(tag[0]);
else if (tag[0] == '?')
alttag[0] = '!';
else {
alttag[0] = 'v';
alttag[1] = tag[0];
alttag[2] = ' ';
alttag[3] = 0;
}
tag = alttag;
}
if (!show_stage) {
fputs(tag, stdout);
} else {
printf("%s%06o %s %d\t",
tag,
ce->ce_mode,
find_unique_abbrev(ce->oid.hash, abbrev),
ce_stage(ce));
}
write_eolinfo(ce, ce->name);
write_name(ce->name);
if (debug_mode) {
const struct stat_data *sd = &ce->ce_stat_data;
printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
tag = alttag;
}
if (!show_stage) {
fputs(tag, stdout);
} else {
printf("%s%06o %s %d\t",
tag,
ce->ce_mode,
find_unique_abbrev(ce->oid.hash,abbrev),
ce_stage(ce));
}
write_eolinfo(ce, ce->name);
write_name(ce->name);
if (debug_mode) {
const struct stat_data *sd = &ce->ce_stat_data;
printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
strbuf_release(&name);
}
static void show_ru_info(void)
@ -468,6 +547,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
N_("make the output relative to the project top directory"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
N_("recurse through submodules")),
OPT_BOOL(0, "error-unmatch", &error_unmatch,
N_("if any <file> is not in the index, treat this as an error")),
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
@ -484,6 +565,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
super_prefix = get_super_prefix();
git_config(git_default_config, NULL);
if (read_cache() < 0)
@ -519,13 +601,32 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
if (recurse_submodules)
compile_submodule_options(&dir, show_tag);
if (recurse_submodules &&
(show_stage || show_deleted || show_others || show_unmerged ||
show_killed || show_modified || show_resolve_undo || with_tree))
die("ls-files --recurse-submodules unsupported mode");
if (recurse_submodules && error_unmatch)
die("ls-files --recurse-submodules does not support "
"--error-unmatch");
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD |
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
prefix, argv);
/* Find common prefix for all pathspec's */
max_prefix = common_prefix(&pathspec);
/*
* Find common prefix for all pathspec's
* This is used as a performance optimization which unfortunately cannot
* be done when recursing into submodules
*/
if (recurse_submodules)
max_prefix = NULL;
else
max_prefix = common_prefix(&pathspec);
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */