Skip to content

Commit 636c0c5

Browse files
committed
Merge branch 'bw/ls-files-recurse-submodules' into pu
"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: optionally recurse into submodules
2 parents 7301050 + b40fcd1 commit 636c0c5

File tree

5 files changed

+381
-43
lines changed

5 files changed

+381
-43
lines changed

Documentation/git-ls-files.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ SYNOPSIS
1818
[--exclude-per-directory=<file>]
1919
[--exclude-standard]
2020
[--error-unmatch] [--with-tree=<tree-ish>]
21-
[--full-name] [--abbrev] [--] [<file>...]
21+
[--full-name] [--recurse-submodules]
22+
[--submodule-prefix=<path>]
23+
[--abbrev] [--] [<file>...]
2224

2325
DESCRIPTION
2426
-----------
@@ -137,6 +139,13 @@ a space) at the start of each line:
137139
option forces paths to be output relative to the project
138140
top directory.
139141

142+
--recurse-submodules::
143+
Recursively calls ls-files on each submodule in the repository.
144+
Currently there is only support for the --cached mode.
145+
146+
--submodule-prefix=<path>::
147+
Prepend the provided path to the output of each file
148+
140149
--abbrev[=<n>]::
141150
Instead of showing the full 40-byte hexadecimal object
142151
lines, show only a partial prefix.

builtin/ls-files.c

Lines changed: 129 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "resolve-undo.h"
1515
#include "string-list.h"
1616
#include "pathspec.h"
17+
#include "run-command.h"
1718

1819
static int abbrev;
1920
static int show_deleted;
@@ -28,6 +29,8 @@ static int show_valid_bit;
2829
static int line_terminator = '\n';
2930
static int debug_mode;
3031
static int show_eol;
32+
static const char *submodule_prefix;
33+
static int recurse_submodules;
3134

3235
static const char *prefix;
3336
static int max_prefix_len;
@@ -67,6 +70,21 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path)
6770

6871
static void write_name(const char *name)
6972
{
73+
/*
74+
* NEEDSWORK: To make this thread-safe, full_name would have to be owned
75+
* by the caller.
76+
*
77+
* full_name get reused across output lines to minimize the allocation
78+
* churn.
79+
*/
80+
static struct strbuf full_name = STRBUF_INIT;
81+
if (submodule_prefix && *submodule_prefix) {
82+
strbuf_reset(&full_name);
83+
strbuf_addstr(&full_name, submodule_prefix);
84+
strbuf_addstr(&full_name, name);
85+
name = full_name.buf;
86+
}
87+
7088
/*
7189
* With "--full-name", prefix_len=0; this caller needs to pass
7290
* an empty string in that case (a NULL is good for "").
@@ -152,55 +170,106 @@ static void show_killed_files(struct dir_struct *dir)
152170
}
153171
}
154172

173+
/**
174+
* Recursively call ls-files on a submodule
175+
*/
176+
static void show_gitlink(const struct cache_entry *ce)
177+
{
178+
struct child_process cp = CHILD_PROCESS_INIT;
179+
int status;
180+
int i;
181+
182+
argv_array_push(&cp.args, "ls-files");
183+
argv_array_push(&cp.args, "--recurse-submodules");
184+
argv_array_pushf(&cp.args, "--submodule-prefix=%s%s/",
185+
submodule_prefix ? submodule_prefix : "",
186+
ce->name);
187+
/* add options */
188+
if (show_eol)
189+
argv_array_push(&cp.args, "--eol");
190+
if (show_valid_bit)
191+
argv_array_push(&cp.args, "-v");
192+
if (show_stage)
193+
argv_array_push(&cp.args, "--stage");
194+
if (show_cached)
195+
argv_array_push(&cp.args, "--cached");
196+
if (debug_mode)
197+
argv_array_push(&cp.args, "--debug");
198+
199+
/*
200+
* Pass in the original pathspec args. The submodule will be
201+
* responsible for prepending the 'submodule_prefix' prior to comparing
202+
* against the pathspec for matches.
203+
*/
204+
argv_array_push(&cp.args, "--");
205+
for (i = 0; i < pathspec.nr; i++)
206+
argv_array_push(&cp.args, pathspec.items[i].original);
207+
208+
cp.git_cmd = 1;
209+
cp.dir = ce->name;
210+
status = run_command(&cp);
211+
if (status)
212+
exit(status);
213+
}
214+
155215
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
156216
{
217+
struct strbuf name = STRBUF_INIT;
157218
int len = max_prefix_len;
219+
if (submodule_prefix)
220+
strbuf_addstr(&name, submodule_prefix);
221+
strbuf_addstr(&name, ce->name);
158222

159223
if (len >= ce_namelen(ce))
160224
die("git ls-files: internal error - cache entry not superset of prefix");
161225

162-
if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce),
163-
len, ps_matched,
164-
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
165-
return;
226+
if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
227+
submodule_path_match(&pathspec, name.buf, ps_matched)) {
228+
show_gitlink(ce);
229+
} else if (match_pathspec(&pathspec, name.buf, name.len,
230+
len, ps_matched,
231+
S_ISDIR(ce->ce_mode) ||
232+
S_ISGITLINK(ce->ce_mode))) {
233+
if (tag && *tag && show_valid_bit &&
234+
(ce->ce_flags & CE_VALID)) {
235+
static char alttag[4];
236+
memcpy(alttag, tag, 3);
237+
if (isalpha(tag[0]))
238+
alttag[0] = tolower(tag[0]);
239+
else if (tag[0] == '?')
240+
alttag[0] = '!';
241+
else {
242+
alttag[0] = 'v';
243+
alttag[1] = tag[0];
244+
alttag[2] = ' ';
245+
alttag[3] = 0;
246+
}
247+
tag = alttag;
248+
}
166249

167-
if (tag && *tag && show_valid_bit &&
168-
(ce->ce_flags & CE_VALID)) {
169-
static char alttag[4];
170-
memcpy(alttag, tag, 3);
171-
if (isalpha(tag[0]))
172-
alttag[0] = tolower(tag[0]);
173-
else if (tag[0] == '?')
174-
alttag[0] = '!';
175-
else {
176-
alttag[0] = 'v';
177-
alttag[1] = tag[0];
178-
alttag[2] = ' ';
179-
alttag[3] = 0;
250+
if (!show_stage) {
251+
fputs(tag, stdout);
252+
} else {
253+
printf("%s%06o %s %d\t",
254+
tag,
255+
ce->ce_mode,
256+
find_unique_abbrev(ce->oid.hash, abbrev),
257+
ce_stage(ce));
258+
}
259+
write_eolinfo(ce, ce->name);
260+
write_name(ce->name);
261+
if (debug_mode) {
262+
const struct stat_data *sd = &ce->ce_stat_data;
263+
264+
printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
265+
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
266+
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
267+
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
268+
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
180269
}
181-
tag = alttag;
182270
}
183271

184-
if (!show_stage) {
185-
fputs(tag, stdout);
186-
} else {
187-
printf("%s%06o %s %d\t",
188-
tag,
189-
ce->ce_mode,
190-
find_unique_abbrev(ce->oid.hash,abbrev),
191-
ce_stage(ce));
192-
}
193-
write_eolinfo(ce, ce->name);
194-
write_name(ce->name);
195-
if (debug_mode) {
196-
const struct stat_data *sd = &ce->ce_stat_data;
197-
198-
printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
199-
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
200-
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
201-
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
202-
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
203-
}
272+
strbuf_release(&name);
204273
}
205274

206275
static void show_ru_info(void)
@@ -468,6 +537,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
468537
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
469538
N_("make the output relative to the project top directory"),
470539
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
540+
OPT_STRING(0, "submodule-prefix", &submodule_prefix,
541+
N_("path"), N_("prepend <path> to each file")),
542+
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
543+
N_("recurse through submodules")),
471544
OPT_BOOL(0, "error-unmatch", &error_unmatch,
472545
N_("if any <file> is not in the index, treat this as an error")),
473546
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
@@ -519,13 +592,29 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
519592
if (require_work_tree && !is_inside_work_tree())
520593
setup_work_tree();
521594

595+
if (recurse_submodules &&
596+
(show_deleted || show_others || show_unmerged ||
597+
show_killed || show_modified || show_resolve_undo))
598+
die("ls-files --recurse-submodules unsupported mode");
599+
600+
if (recurse_submodules && error_unmatch)
601+
die("ls-files --recurse-submodules does not support "
602+
"--error-unmatch");
603+
522604
parse_pathspec(&pathspec, 0,
523605
PATHSPEC_PREFER_CWD |
524606
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
525607
prefix, argv);
526608

527-
/* Find common prefix for all pathspec's */
528-
max_prefix = common_prefix(&pathspec);
609+
/*
610+
* Find common prefix for all pathspec's
611+
* This is used as a performance optimization which unfortunately cannot
612+
* be done when recursing into submodules
613+
*/
614+
if (recurse_submodules)
615+
max_prefix = NULL;
616+
else
617+
max_prefix = common_prefix(&pathspec);
529618
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
530619

531620
/* Treat unmatching pathspec elements as errors */

dir.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,9 @@ static int match_attrs(const char *name, int namelen,
239239
return 1;
240240
}
241241

242-
#define DO_MATCH_EXCLUDE 1
243-
#define DO_MATCH_DIRECTORY 2
242+
#define DO_MATCH_EXCLUDE (1<<0)
243+
#define DO_MATCH_DIRECTORY (1<<1)
244+
#define DO_MATCH_SUBMODULE (1<<2)
244245

245246
/*
246247
* Does 'match' match the given name?
@@ -318,6 +319,29 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
318319
item->nowildcard_len - prefix))
319320
return MATCHED_FNMATCH;
320321

322+
/* Perform checks to see if "name" is a super set of the pathspec */
323+
if (flags & DO_MATCH_SUBMODULE) {
324+
/* Check if the name is a literal prefix of the pathspec */
325+
if ((item->match[namelen] == '/') &&
326+
!ps_strncmp(item, match, name, namelen))
327+
return MATCHED_RECURSIVELY;
328+
329+
/*
330+
* Here is where we would perform a wildmatch to check if
331+
* "name" can be matched as a directory (or a prefix) against
332+
* the pathspec. Since wildmatch doesn't have this capability
333+
* at the present we have to punt and say that it is a match,
334+
* esentially returning a false positive (as long as "name"
335+
* matches upto the first wild character).
336+
* The submodules themselves will be able to perform more
337+
* accurate matching to determine if the pathspec matches.
338+
*/
339+
if (item->nowildcard_len < item->len &&
340+
!ps_strncmp(item, match, name,
341+
item->nowildcard_len - prefix))
342+
return MATCHED_RECURSIVELY;
343+
}
344+
321345
return 0;
322346
}
323347

@@ -421,6 +445,21 @@ int match_pathspec(const struct pathspec *ps,
421445
return negative ? 0 : positive;
422446
}
423447

448+
/**
449+
* Check if a submodule is a superset of the pathspec
450+
*/
451+
int submodule_path_match(const struct pathspec *ps,
452+
const char *submodule_name,
453+
char *seen)
454+
{
455+
int matched = do_match_pathspec(ps, submodule_name,
456+
strlen(submodule_name),
457+
0, seen,
458+
DO_MATCH_DIRECTORY |
459+
DO_MATCH_SUBMODULE);
460+
return matched;
461+
}
462+
424463
int report_path_error(const char *ps_matched,
425464
const struct pathspec *pathspec,
426465
const char *prefix)

dir.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ extern int git_fnmatch(const struct pathspec_item *item,
304304
const char *pattern, const char *string,
305305
int prefix);
306306

307+
extern int submodule_path_match(const struct pathspec *ps,
308+
const char *submodule_name,
309+
char *seen);
310+
307311
static inline int ce_path_match(const struct cache_entry *ce,
308312
const struct pathspec *pathspec,
309313
char *seen)

0 commit comments

Comments
 (0)