Skip to content

Commit 1c2b1f7

Browse files
committed
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
2 parents 2bee56b + 75a6315 commit 1c2b1f7

File tree

9 files changed

+452
-44
lines changed

9 files changed

+452
-44
lines changed

Documentation/git-ls-files.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ 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+
[--abbrev] [--] [<file>...]
2223

2324
DESCRIPTION
2425
-----------
@@ -137,6 +138,10 @@ a space) at the start of each line:
137138
option forces paths to be output relative to the project
138139
top directory.
139140

141+
--recurse-submodules::
142+
Recursively calls ls-files on each submodule in the repository.
143+
Currently there is only support for the --cached mode.
144+
140145
--abbrev[=<n>]::
141146
Instead of showing the full 40-byte hexadecimal object
142147
lines, show only a partial prefix.

Documentation/git.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ SYNOPSIS
1313
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
1414
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
1515
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
16+
[--super-prefix=<path>]
1617
<command> [<args>]
1718

1819
DESCRIPTION
@@ -602,6 +603,11 @@ foo.bar= ...`) sets `foo.bar` to the empty string.
602603
details. Equivalent to setting the `GIT_NAMESPACE` environment
603604
variable.
604605

606+
--super-prefix=<path>::
607+
Currently for internal use only. Set a prefix which gives a path from
608+
above a repository down to its root. One use is to give submodules
609+
context about the superproject that invoked it.
610+
605611
--bare::
606612
Treat the repository as a bare repository. If GIT_DIR
607613
environment is not set, it is set to the current working

builtin/ls-files.c

Lines changed: 141 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,8 +29,11 @@ static int show_valid_bit;
2829
static int line_terminator = '\n';
2930
static int debug_mode;
3031
static int show_eol;
32+
static int recurse_submodules;
33+
static struct argv_array submodules_options = ARGV_ARRAY_INIT;
3134

3235
static const char *prefix;
36+
static const char *super_prefix;
3337
static int max_prefix_len;
3438
static int prefix_len;
3539
static struct pathspec pathspec;
@@ -67,12 +71,25 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path)
6771

6872
static void write_name(const char *name)
6973
{
74+
/*
75+
* Prepend the super_prefix to name to construct the full_name to be
76+
* written.
77+
*/
78+
struct strbuf full_name = STRBUF_INIT;
79+
if (super_prefix) {
80+
strbuf_addstr(&full_name, super_prefix);
81+
strbuf_addstr(&full_name, name);
82+
name = full_name.buf;
83+
}
84+
7085
/*
7186
* With "--full-name", prefix_len=0; this caller needs to pass
7287
* an empty string in that case (a NULL is good for "").
7388
*/
7489
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
7590
stdout, line_terminator);
91+
92+
strbuf_release(&full_name);
7693
}
7794

7895
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)
152169
}
153170
}
154171

172+
/*
173+
* Compile an argv_array with all of the options supported by --recurse_submodules
174+
*/
175+
static void compile_submodule_options(const struct dir_struct *dir, int show_tag)
176+
{
177+
if (line_terminator == '\0')
178+
argv_array_push(&submodules_options, "-z");
179+
if (show_tag)
180+
argv_array_push(&submodules_options, "-t");
181+
if (show_valid_bit)
182+
argv_array_push(&submodules_options, "-v");
183+
if (show_cached)
184+
argv_array_push(&submodules_options, "--cached");
185+
if (show_eol)
186+
argv_array_push(&submodules_options, "--eol");
187+
if (debug_mode)
188+
argv_array_push(&submodules_options, "--debug");
189+
}
190+
191+
/**
192+
* Recursively call ls-files on a submodule
193+
*/
194+
static void show_gitlink(const struct cache_entry *ce)
195+
{
196+
struct child_process cp = CHILD_PROCESS_INIT;
197+
int status;
198+
int i;
199+
200+
argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
201+
super_prefix ? super_prefix : "",
202+
ce->name);
203+
argv_array_push(&cp.args, "ls-files");
204+
argv_array_push(&cp.args, "--recurse-submodules");
205+
206+
/* add supported options */
207+
argv_array_pushv(&cp.args, submodules_options.argv);
208+
209+
/*
210+
* Pass in the original pathspec args. The submodule will be
211+
* responsible for prepending the 'submodule_prefix' prior to comparing
212+
* against the pathspec for matches.
213+
*/
214+
argv_array_push(&cp.args, "--");
215+
for (i = 0; i < pathspec.nr; i++)
216+
argv_array_push(&cp.args, pathspec.items[i].original);
217+
218+
cp.git_cmd = 1;
219+
cp.dir = ce->name;
220+
status = run_command(&cp);
221+
if (status)
222+
exit(status);
223+
}
224+
155225
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
156226
{
227+
struct strbuf name = STRBUF_INIT;
157228
int len = max_prefix_len;
229+
if (super_prefix)
230+
strbuf_addstr(&name, super_prefix);
231+
strbuf_addstr(&name, ce->name);
158232

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

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;
236+
if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
237+
submodule_path_match(&pathspec, name.buf, ps_matched)) {
238+
show_gitlink(ce);
239+
} else if (match_pathspec(&pathspec, name.buf, name.len,
240+
len, ps_matched,
241+
S_ISDIR(ce->ce_mode) ||
242+
S_ISGITLINK(ce->ce_mode))) {
243+
if (tag && *tag && show_valid_bit &&
244+
(ce->ce_flags & CE_VALID)) {
245+
static char alttag[4];
246+
memcpy(alttag, tag, 3);
247+
if (isalpha(tag[0]))
248+
alttag[0] = tolower(tag[0]);
249+
else if (tag[0] == '?')
250+
alttag[0] = '!';
251+
else {
252+
alttag[0] = 'v';
253+
alttag[1] = tag[0];
254+
alttag[2] = ' ';
255+
alttag[3] = 0;
256+
}
257+
tag = alttag;
258+
}
166259

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;
260+
if (!show_stage) {
261+
fputs(tag, stdout);
262+
} else {
263+
printf("%s%06o %s %d\t",
264+
tag,
265+
ce->ce_mode,
266+
find_unique_abbrev(ce->oid.hash, abbrev),
267+
ce_stage(ce));
268+
}
269+
write_eolinfo(ce, ce->name);
270+
write_name(ce->name);
271+
if (debug_mode) {
272+
const struct stat_data *sd = &ce->ce_stat_data;
273+
274+
printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
275+
printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
276+
printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
277+
printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
278+
printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
180279
}
181-
tag = alttag;
182280
}
183281

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-
}
282+
strbuf_release(&name);
204283
}
205284

206285
static void show_ru_info(void)
@@ -468,6 +547,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
468547
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
469548
N_("make the output relative to the project top directory"),
470549
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
550+
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
551+
N_("recurse through submodules")),
471552
OPT_BOOL(0, "error-unmatch", &error_unmatch,
472553
N_("if any <file> is not in the index, treat this as an error")),
473554
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)
484565
prefix = cmd_prefix;
485566
if (prefix)
486567
prefix_len = strlen(prefix);
568+
super_prefix = get_super_prefix();
487569
git_config(git_default_config, NULL);
488570

489571
if (read_cache() < 0)
@@ -519,13 +601,32 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
519601
if (require_work_tree && !is_inside_work_tree())
520602
setup_work_tree();
521603

604+
if (recurse_submodules)
605+
compile_submodule_options(&dir, show_tag);
606+
607+
if (recurse_submodules &&
608+
(show_stage || show_deleted || show_others || show_unmerged ||
609+
show_killed || show_modified || show_resolve_undo || with_tree))
610+
die("ls-files --recurse-submodules unsupported mode");
611+
612+
if (recurse_submodules && error_unmatch)
613+
die("ls-files --recurse-submodules does not support "
614+
"--error-unmatch");
615+
522616
parse_pathspec(&pathspec, 0,
523617
PATHSPEC_PREFER_CWD |
524618
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
525619
prefix, argv);
526620

527-
/* Find common prefix for all pathspec's */
528-
max_prefix = common_prefix(&pathspec);
621+
/*
622+
* Find common prefix for all pathspec's
623+
* This is used as a performance optimization which unfortunately cannot
624+
* be done when recursing into submodules
625+
*/
626+
if (recurse_submodules)
627+
max_prefix = NULL;
628+
else
629+
max_prefix = common_prefix(&pathspec);
529630
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
530631

531632
/* Treat unmatching pathspec elements as errors */

cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ static inline enum object_type object_type(unsigned int mode)
409409
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
410410
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
411411
#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
412+
#define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
412413
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
413414
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
414415
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -476,6 +477,7 @@ extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
476477
extern int get_common_dir(struct strbuf *sb, const char *gitdir);
477478
extern const char *get_git_namespace(void);
478479
extern const char *strip_namespace(const char *namespaced_ref);
480+
extern const char *get_super_prefix(void);
479481
extern const char *get_git_work_tree(void);
480482

481483
/*

dir.c

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,9 @@ int within_depth(const char *name, int namelen,
207207
return 1;
208208
}
209209

210-
#define DO_MATCH_EXCLUDE 1
211-
#define DO_MATCH_DIRECTORY 2
210+
#define DO_MATCH_EXCLUDE (1<<0)
211+
#define DO_MATCH_DIRECTORY (1<<1)
212+
#define DO_MATCH_SUBMODULE (1<<2)
212213

213214
/*
214215
* Does 'match' match the given name?
@@ -283,6 +284,32 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
283284
item->nowildcard_len - prefix))
284285
return MATCHED_FNMATCH;
285286

287+
/* Perform checks to see if "name" is a super set of the pathspec */
288+
if (flags & DO_MATCH_SUBMODULE) {
289+
/* name is a literal prefix of the pathspec */
290+
if ((namelen < matchlen) &&
291+
(match[namelen] == '/') &&
292+
!ps_strncmp(item, match, name, namelen))
293+
return MATCHED_RECURSIVELY;
294+
295+
/* name" doesn't match up to the first wild character */
296+
if (item->nowildcard_len < item->len &&
297+
ps_strncmp(item, match, name,
298+
item->nowildcard_len - prefix))
299+
return 0;
300+
301+
/*
302+
* Here is where we would perform a wildmatch to check if
303+
* "name" can be matched as a directory (or a prefix) against
304+
* the pathspec. Since wildmatch doesn't have this capability
305+
* at the present we have to punt and say that it is a match,
306+
* potentially returning a false positive
307+
* The submodules themselves will be able to perform more
308+
* accurate matching to determine if the pathspec matches.
309+
*/
310+
return MATCHED_RECURSIVELY;
311+
}
312+
286313
return 0;
287314
}
288315

@@ -386,6 +413,21 @@ int match_pathspec(const struct pathspec *ps,
386413
return negative ? 0 : positive;
387414
}
388415

416+
/**
417+
* Check if a submodule is a superset of the pathspec
418+
*/
419+
int submodule_path_match(const struct pathspec *ps,
420+
const char *submodule_name,
421+
char *seen)
422+
{
423+
int matched = do_match_pathspec(ps, submodule_name,
424+
strlen(submodule_name),
425+
0, seen,
426+
DO_MATCH_DIRECTORY |
427+
DO_MATCH_SUBMODULE);
428+
return matched;
429+
}
430+
389431
int report_path_error(const char *ps_matched,
390432
const struct pathspec *pathspec,
391433
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)