Skip to content

Commit 0e91b3f

Browse files
bmwillgitster
authored andcommitted
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. Also added a submodule-prefix command in order to prepend paths to child processes. Signed-off-by: Brandon Williams <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 35f6318 commit 0e91b3f

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
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: 61 additions & 0 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,6 +170,26 @@ 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+
181+
argv_array_push(&cp.args, "ls-files");
182+
argv_array_push(&cp.args, "--recurse-submodules");
183+
argv_array_pushf(&cp.args, "--submodule-prefix=%s%s/",
184+
submodule_prefix ? submodule_prefix : "",
185+
ce->name);
186+
cp.git_cmd = 1;
187+
cp.dir = ce->name;
188+
status = run_command(&cp);
189+
if (status)
190+
exit(status);
191+
}
192+
155193
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
156194
{
157195
int len = max_prefix_len;
@@ -163,6 +201,10 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce)
163201
len, ps_matched,
164202
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
165203
return;
204+
if (recurse_submodules && S_ISGITLINK(ce->ce_mode)) {
205+
show_gitlink(ce);
206+
return;
207+
}
166208

167209
if (tag && *tag && show_valid_bit &&
168210
(ce->ce_flags & CE_VALID)) {
@@ -468,6 +510,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
468510
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
469511
N_("make the output relative to the project top directory"),
470512
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
513+
OPT_STRING(0, "submodule-prefix", &submodule_prefix,
514+
N_("path"), N_("prepend <path> to each file")),
515+
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
516+
N_("recurse through submodules")),
471517
OPT_BOOL(0, "error-unmatch", &error_unmatch,
472518
N_("if any <file> is not in the index, treat this as an error")),
473519
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
@@ -519,6 +565,21 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
519565
if (require_work_tree && !is_inside_work_tree())
520566
setup_work_tree();
521567

568+
if (recurse_submodules &&
569+
(show_stage || show_deleted || show_others || show_unmerged ||
570+
show_killed || show_modified || show_resolve_undo ||
571+
show_valid_bit || show_tag || show_eol))
572+
die("ls-files --recurse-submodules can only be used in "
573+
"--cached mode");
574+
575+
if (recurse_submodules && error_unmatch)
576+
die("ls-files --recurse-submodules does not support "
577+
"--error-unmatch");
578+
579+
if (recurse_submodules && argc)
580+
die("ls-files --recurse-submodules does not support path "
581+
"arguments");
582+
522583
parse_pathspec(&pathspec, 0,
523584
PATHSPEC_PREFER_CWD |
524585
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 path arguments" 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 'can only be used in --cached mode' actual
86+
"
87+
}
88+
89+
test_incompatible_with_recurse_submodules -v
90+
test_incompatible_with_recurse_submodules -t
91+
test_incompatible_with_recurse_submodules --deleted
92+
test_incompatible_with_recurse_submodules --modified
93+
test_incompatible_with_recurse_submodules --others
94+
test_incompatible_with_recurse_submodules --stage
95+
test_incompatible_with_recurse_submodules --killed
96+
test_incompatible_with_recurse_submodules --unmerged
97+
test_incompatible_with_recurse_submodules --eol
98+
99+
test_done

0 commit comments

Comments
 (0)