ls-files: optionally recurse into submodules
Allow ls-files to recognize submodules in order to retrieve a list of files from a repository's submodules. This is done by forking off a process to recursively call ls-files on all submodules. Use top-level --super-prefix option to pass a path to the submodule which it can use to prepend to output or pathspec matching logic. Signed-off-by: Brandon Williams <bmwill@google.com> Reviewed-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Brandon Williams committed
Oct 7, 2016 at 11:18 UTC
e77aa336f116e8ff8a72d034494b3a476b78c3ee
4 files changed
+208
-40
Documentation/git-ls-files.txt
+7
-1
@@ -18,7 +18,8 @@ SYNOPSIS
18
[--exclude-per-directory=<file>]
19
[--exclude-standard]
20
[--error-unmatch] [--with-tree=<tree-ish>]
21
- [--full-name] [--abbrev] [--] [<file>...]
21
+ [--full-name] [--recurse-submodules]
22
+ [--abbrev] [--] [<file>...]
23
24
DESCRIPTION
25
-----------
@@ -137,6 +138,11 @@ a space) at the start of each line:
138
option forces paths to be output relative to the project
139
top directory.
140
141
+--recurse-submodules::
142
+ Recursively calls ls-files on each submodule in the repository.
143
+ Currently there is only support for the --cached mode without a
144
+ pathspec.
145
+
146
--abbrev[=<n>]::
147
Instead of showing the full 40-byte hexadecimal object
148
lines, show only a partial prefix.
builtin/ls-files.c
+100
-38
@@ -14,6 +14,7 @@
14
#include "resolve-undo.h"
15
#include "string-list.h"
16
#include "pathspec.h"
17
+#include "run-command.h"
18
19
static int abbrev;
20
static int show_deleted;
@@ -28,8 +29,10 @@ static int show_valid_bit;
29
static int line_terminator = '\n';
30
static int debug_mode;
31
static int show_eol;
32
+static int recurse_submodules;
33
34
static const char *prefix;
35
+static const char *super_prefix;
36
static int max_prefix_len;
37
static int prefix_len;
38
static struct pathspec pathspec;
@@ -67,12 +70,25 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path)
70
71
static void write_name(const char *name)
72
{
73
+ /*
74
+ * Prepend the super_prefix to name to construct the full_name to be
75
+ * written.
76
+ */
77
+ struct strbuf full_name = STRBUF_INIT;
78
+ if (super_prefix) {
79
+ strbuf_addstr(&full_name, super_prefix);
80
+ strbuf_addstr(&full_name, name);
81
+ name = full_name.buf;
82
+ }
83
+
84
/*
85
* With "--full-name", prefix_len=0; this caller needs to pass
86
* an empty string in that case (a NULL is good for "").
87
*/
88
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
89
stdout, line_terminator);
90
+
91
+ strbuf_release(&full_name);
92
}
93
94
static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -152,55 +168,84 @@ static void show_killed_files(struct dir_struct *dir)
168
}
169
}
170
171
+/**
172
+ * Recursively call ls-files on a submodule
173
+ */
174
+static void show_gitlink(const struct cache_entry *ce)
175
+{
176
+ struct child_process cp = CHILD_PROCESS_INIT;
177
+ int status;
178
+
179
+ argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
180
+ super_prefix ? super_prefix : "",
181
+ ce->name);
182
+ argv_array_push(&cp.args, "ls-files");
183
+ argv_array_push(&cp.args, "--recurse-submodules");
184
+
185
+ cp.git_cmd = 1;
186
+ cp.dir = ce->name;
187
+ status = run_command(&cp);
188
+ if (status)
189
+ exit(status);
190
+}
191
+
192
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
193
{
194
+ struct strbuf name = STRBUF_INIT;
195
int len = max_prefix_len;
196
+ if (super_prefix)
197
+ strbuf_addstr(&name, super_prefix);
198
+ strbuf_addstr(&name, ce->name);
199
200
if (len >= ce_namelen(ce))
201
die("git ls-files: internal error - cache entry not superset of prefix");
202
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;
203
+ if (recurse_submodules && S_ISGITLINK(ce->ce_mode)) {
204
+ show_gitlink(ce);
205
+ } else if (match_pathspec(&pathspec, name.buf, name.len,
206
+ len, ps_matched,
207
+ S_ISDIR(ce->ce_mode) ||
208
+ S_ISGITLINK(ce->ce_mode))) {
209
+ if (tag && *tag && show_valid_bit &&
210
+ (ce->ce_flags & CE_VALID)) {
211
+ static char alttag[4];
212
+ memcpy(alttag, tag, 3);
213
+ if (isalpha(tag[0]))
214
+ alttag[0] = tolower(tag[0]);
215
+ else if (tag[0] == '?')
216
+ alttag[0] = '!';
217
+ else {
218
+ alttag[0] = 'v';
219
+ alttag[1] = tag[0];
220
+ alttag[2] = ' ';
221
+ alttag[3] = 0;
222
+ }
223
+ tag = alttag;
224
+ }
225
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;
226
+ if (!show_stage) {
227
+ fputs(tag, stdout);
228
+ } else {
229
+ printf("%s%06o %s %d\t",
230
+ tag,
231
+ ce->ce_mode,
232
+ find_unique_abbrev(ce->sha1,abbrev),
233
+ ce_stage(ce));
234
+ }
235
+ write_eolinfo(ce, ce->name);
236
+ write_name(ce->name);
237
+ if (debug_mode) {
238
+ const struct stat_data *sd = &ce->ce_stat_data;
239
+
240
+ printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
241
+ printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
242
+ printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
243
+ printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
244
+ printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
245
}
181
- tag = alttag;
246
}
247
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->sha1,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
- }
248
+ strbuf_release(&name);
249
}
250
251
static void show_ru_info(void)
@@ -468,6 +513,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
513
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
514
N_("make the output relative to the project top directory"),
515
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
516
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
517
+ N_("recurse through submodules")),
518
OPT_BOOL(0, "error-unmatch", &error_unmatch,
519
N_("if any <file> is not in the index, treat this as an error")),
520
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
@@ -484,6 +531,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
531
prefix = cmd_prefix;
532
if (prefix)
533
prefix_len = strlen(prefix);
534
+ super_prefix = get_super_prefix();
535
git_config(git_default_config, NULL);
536
537
if (read_cache() < 0)
@@ -519,6 +567,20 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
567
if (require_work_tree && !is_inside_work_tree())
568
setup_work_tree();
569
570
+ if (recurse_submodules &&
571
+ (show_stage || show_deleted || show_others || show_unmerged ||
572
+ show_killed || show_modified || show_resolve_undo ||
573
+ show_valid_bit || show_tag || show_eol || with_tree ||
574
+ (line_terminator == '\0')))
575
+ die("ls-files --recurse-submodules unsupported mode");
576
+
577
+ if (recurse_submodules && error_unmatch)
578
+ die("ls-files --recurse-submodules does not support "
579
+ "--error-unmatch");
580
+
581
+ if (recurse_submodules && argc)
582
+ die("ls-files --recurse-submodules does not support pathspec");
583
+
584
parse_pathspec(&pathspec, 0,
585
PATHSPEC_PREFER_CWD |
586
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
git.c
+1
-1
@@ -443,7 +443,7 @@ static struct cmd_struct commands[] = {
443
{ "init-db", cmd_init_db },
444
{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
445
{ "log", cmd_log, RUN_SETUP },
446
- { "ls-files", cmd_ls_files, RUN_SETUP },
446
+ { "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX },
447
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
448
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
449
{ "mailinfo", cmd_mailinfo },
t/t3007-ls-files-recurse-submodules.sh
new
+100
@@ -0,0 +1,100 @@
1
+#!/bin/sh
2
+
3
+test_description='Test ls-files recurse-submodules feature
4
+
5
+This test verifies the recurse-submodules feature correctly lists files from
6
+submodules.
7
+'
8
+
9
+. ./test-lib.sh
10
+
11
+test_expect_success 'setup directory structure and submodules' '
12
+ echo a >a &&
13
+ mkdir b &&
14
+ echo b >b/b &&
15
+ git add a b &&
16
+ git commit -m "add a and b" &&
17
+ git init submodule &&
18
+ echo c >submodule/c &&
19
+ git -C submodule add c &&
20
+ git -C submodule commit -m "add c" &&
21
+ git submodule add ./submodule &&
22
+ git commit -m "added submodule"
23
+'
24
+
25
+test_expect_success 'ls-files correctly outputs files in submodule' '
26
+ cat >expect <<-\EOF &&
27
+ .gitmodules
28
+ a
29
+ b/b
30
+ submodule/c
31
+ EOF
32
+
33
+ git ls-files --recurse-submodules >actual &&
34
+ test_cmp expect actual
35
+'
36
+
37
+test_expect_success 'ls-files does not output files not added to a repo' '
38
+ cat >expect <<-\EOF &&
39
+ .gitmodules
40
+ a
41
+ b/b
42
+ submodule/c
43
+ EOF
44
+
45
+ echo a >not_added &&
46
+ echo b >b/not_added &&
47
+ echo c >submodule/not_added &&
48
+ git ls-files --recurse-submodules >actual &&
49
+ test_cmp expect actual
50
+'
51
+
52
+test_expect_success 'ls-files recurses more than 1 level' '
53
+ cat >expect <<-\EOF &&
54
+ .gitmodules
55
+ a
56
+ b/b
57
+ submodule/.gitmodules
58
+ submodule/c
59
+ submodule/subsub/d
60
+ EOF
61
+
62
+ git init submodule/subsub &&
63
+ echo d >submodule/subsub/d &&
64
+ git -C submodule/subsub add d &&
65
+ git -C submodule/subsub commit -m "add d" &&
66
+ git -C submodule submodule add ./subsub &&
67
+ git -C submodule commit -m "added subsub" &&
68
+ git ls-files --recurse-submodules >actual &&
69
+ test_cmp expect actual
70
+'
71
+
72
+test_expect_success '--recurse-submodules does not support using path arguments' '
73
+ test_must_fail git ls-files --recurse-submodules b 2>actual &&
74
+ test_i18ngrep "does not support pathspec" actual
75
+'
76
+
77
+test_expect_success '--recurse-submodules does not support --error-unmatch' '
78
+ test_must_fail git ls-files --recurse-submodules --error-unmatch 2>actual &&
79
+ test_i18ngrep "does not support --error-unmatch" actual
80
+'
81
+
82
+test_incompatible_with_recurse_submodules () {
83
+ test_expect_success "--recurse-submodules and $1 are incompatible" "
84
+ test_must_fail git ls-files --recurse-submodules $1 2>actual &&
85
+ test_i18ngrep 'unsupported mode' actual
86
+ "
87
+}
88
+
89
+test_incompatible_with_recurse_submodules -z
90
+test_incompatible_with_recurse_submodules -v
91
+test_incompatible_with_recurse_submodules -t
92
+test_incompatible_with_recurse_submodules --deleted
93
+test_incompatible_with_recurse_submodules --modified
94
+test_incompatible_with_recurse_submodules --others
95
+test_incompatible_with_recurse_submodules --stage
96
+test_incompatible_with_recurse_submodules --killed
97
+test_incompatible_with_recurse_submodules --unmerged
98
+test_incompatible_with_recurse_submodules --eol
99
+
100
+test_done