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>
This commit is contained in:
parent
74866d7579
commit
e77aa336f1
@ -18,7 +18,8 @@ SYNOPSIS
|
|||||||
[--exclude-per-directory=<file>]
|
[--exclude-per-directory=<file>]
|
||||||
[--exclude-standard]
|
[--exclude-standard]
|
||||||
[--error-unmatch] [--with-tree=<tree-ish>]
|
[--error-unmatch] [--with-tree=<tree-ish>]
|
||||||
[--full-name] [--abbrev] [--] [<file>...]
|
[--full-name] [--recurse-submodules]
|
||||||
|
[--abbrev] [--] [<file>...]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -137,6 +138,11 @@ a space) at the start of each line:
|
|||||||
option forces paths to be output relative to the project
|
option forces paths to be output relative to the project
|
||||||
top directory.
|
top directory.
|
||||||
|
|
||||||
|
--recurse-submodules::
|
||||||
|
Recursively calls ls-files on each submodule in the repository.
|
||||||
|
Currently there is only support for the --cached mode without a
|
||||||
|
pathspec.
|
||||||
|
|
||||||
--abbrev[=<n>]::
|
--abbrev[=<n>]::
|
||||||
Instead of showing the full 40-byte hexadecimal object
|
Instead of showing the full 40-byte hexadecimal object
|
||||||
lines, show only a partial prefix.
|
lines, show only a partial prefix.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "resolve-undo.h"
|
#include "resolve-undo.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "pathspec.h"
|
#include "pathspec.h"
|
||||||
|
#include "run-command.h"
|
||||||
|
|
||||||
static int abbrev;
|
static int abbrev;
|
||||||
static int show_deleted;
|
static int show_deleted;
|
||||||
@ -28,8 +29,10 @@ static int show_valid_bit;
|
|||||||
static int line_terminator = '\n';
|
static int line_terminator = '\n';
|
||||||
static int debug_mode;
|
static int debug_mode;
|
||||||
static int show_eol;
|
static int show_eol;
|
||||||
|
static int recurse_submodules;
|
||||||
|
|
||||||
static const char *prefix;
|
static const char *prefix;
|
||||||
|
static const char *super_prefix;
|
||||||
static int max_prefix_len;
|
static int max_prefix_len;
|
||||||
static int prefix_len;
|
static int prefix_len;
|
||||||
static struct pathspec pathspec;
|
static struct pathspec pathspec;
|
||||||
@ -67,12 +70,25 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path)
|
|||||||
|
|
||||||
static void write_name(const char *name)
|
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
|
* With "--full-name", prefix_len=0; this caller needs to pass
|
||||||
* an empty string in that case (a NULL is good for "").
|
* an empty string in that case (a NULL is good for "").
|
||||||
*/
|
*/
|
||||||
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
|
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
|
||||||
stdout, line_terminator);
|
stdout, line_terminator);
|
||||||
|
|
||||||
|
strbuf_release(&full_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_dir_entry(const char *tag, struct dir_entry *ent)
|
static void show_dir_entry(const char *tag, struct dir_entry *ent)
|
||||||
@ -152,18 +168,44 @@ static void show_killed_files(struct dir_struct *dir)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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)
|
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
|
||||||
{
|
{
|
||||||
|
struct strbuf name = STRBUF_INIT;
|
||||||
int len = max_prefix_len;
|
int len = max_prefix_len;
|
||||||
|
if (super_prefix)
|
||||||
|
strbuf_addstr(&name, super_prefix);
|
||||||
|
strbuf_addstr(&name, ce->name);
|
||||||
|
|
||||||
if (len >= ce_namelen(ce))
|
if (len >= ce_namelen(ce))
|
||||||
die("git ls-files: internal error - cache entry not superset of prefix");
|
die("git ls-files: internal error - cache entry not superset of prefix");
|
||||||
|
|
||||||
if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce),
|
if (recurse_submodules && S_ISGITLINK(ce->ce_mode)) {
|
||||||
|
show_gitlink(ce);
|
||||||
|
} else if (match_pathspec(&pathspec, name.buf, name.len,
|
||||||
len, ps_matched,
|
len, ps_matched,
|
||||||
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
|
S_ISDIR(ce->ce_mode) ||
|
||||||
return;
|
S_ISGITLINK(ce->ce_mode))) {
|
||||||
|
|
||||||
if (tag && *tag && show_valid_bit &&
|
if (tag && *tag && show_valid_bit &&
|
||||||
(ce->ce_flags & CE_VALID)) {
|
(ce->ce_flags & CE_VALID)) {
|
||||||
static char alttag[4];
|
static char alttag[4];
|
||||||
@ -203,6 +245,9 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strbuf_release(&name);
|
||||||
|
}
|
||||||
|
|
||||||
static void show_ru_info(void)
|
static void show_ru_info(void)
|
||||||
{
|
{
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
@ -468,6 +513,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
|
|||||||
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
|
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
|
||||||
N_("make the output relative to the project top directory"),
|
N_("make the output relative to the project top directory"),
|
||||||
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
|
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,
|
OPT_BOOL(0, "error-unmatch", &error_unmatch,
|
||||||
N_("if any <file> is not in the index, treat this as an error")),
|
N_("if any <file> is not in the index, treat this as an error")),
|
||||||
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
|
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)
|
|||||||
prefix = cmd_prefix;
|
prefix = cmd_prefix;
|
||||||
if (prefix)
|
if (prefix)
|
||||||
prefix_len = strlen(prefix);
|
prefix_len = strlen(prefix);
|
||||||
|
super_prefix = get_super_prefix();
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
|
||||||
if (read_cache() < 0)
|
if (read_cache() < 0)
|
||||||
@ -519,6 +567,20 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
|
|||||||
if (require_work_tree && !is_inside_work_tree())
|
if (require_work_tree && !is_inside_work_tree())
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
|
|
||||||
|
if (recurse_submodules &&
|
||||||
|
(show_stage || show_deleted || show_others || show_unmerged ||
|
||||||
|
show_killed || show_modified || show_resolve_undo ||
|
||||||
|
show_valid_bit || show_tag || show_eol || with_tree ||
|
||||||
|
(line_terminator == '\0')))
|
||||||
|
die("ls-files --recurse-submodules unsupported mode");
|
||||||
|
|
||||||
|
if (recurse_submodules && error_unmatch)
|
||||||
|
die("ls-files --recurse-submodules does not support "
|
||||||
|
"--error-unmatch");
|
||||||
|
|
||||||
|
if (recurse_submodules && argc)
|
||||||
|
die("ls-files --recurse-submodules does not support pathspec");
|
||||||
|
|
||||||
parse_pathspec(&pathspec, 0,
|
parse_pathspec(&pathspec, 0,
|
||||||
PATHSPEC_PREFER_CWD |
|
PATHSPEC_PREFER_CWD |
|
||||||
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
|
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
|
||||||
|
2
git.c
2
git.c
@ -443,7 +443,7 @@ static struct cmd_struct commands[] = {
|
|||||||
{ "init-db", cmd_init_db },
|
{ "init-db", cmd_init_db },
|
||||||
{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
|
{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
|
||||||
{ "log", cmd_log, RUN_SETUP },
|
{ "log", cmd_log, RUN_SETUP },
|
||||||
{ "ls-files", cmd_ls_files, RUN_SETUP },
|
{ "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX },
|
||||||
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
|
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
|
||||||
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
|
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
|
||||||
{ "mailinfo", cmd_mailinfo },
|
{ "mailinfo", cmd_mailinfo },
|
||||||
|
100
t/t3007-ls-files-recurse-submodules.sh
Executable file
100
t/t3007-ls-files-recurse-submodules.sh
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='Test ls-files recurse-submodules feature
|
||||||
|
|
||||||
|
This test verifies the recurse-submodules feature correctly lists files from
|
||||||
|
submodules.
|
||||||
|
'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup directory structure and submodules' '
|
||||||
|
echo a >a &&
|
||||||
|
mkdir b &&
|
||||||
|
echo b >b/b &&
|
||||||
|
git add a b &&
|
||||||
|
git commit -m "add a and b" &&
|
||||||
|
git init submodule &&
|
||||||
|
echo c >submodule/c &&
|
||||||
|
git -C submodule add c &&
|
||||||
|
git -C submodule commit -m "add c" &&
|
||||||
|
git submodule add ./submodule &&
|
||||||
|
git commit -m "added submodule"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'ls-files correctly outputs files in submodule' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
.gitmodules
|
||||||
|
a
|
||||||
|
b/b
|
||||||
|
submodule/c
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git ls-files --recurse-submodules >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'ls-files does not output files not added to a repo' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
.gitmodules
|
||||||
|
a
|
||||||
|
b/b
|
||||||
|
submodule/c
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo a >not_added &&
|
||||||
|
echo b >b/not_added &&
|
||||||
|
echo c >submodule/not_added &&
|
||||||
|
git ls-files --recurse-submodules >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'ls-files recurses more than 1 level' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
.gitmodules
|
||||||
|
a
|
||||||
|
b/b
|
||||||
|
submodule/.gitmodules
|
||||||
|
submodule/c
|
||||||
|
submodule/subsub/d
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git init submodule/subsub &&
|
||||||
|
echo d >submodule/subsub/d &&
|
||||||
|
git -C submodule/subsub add d &&
|
||||||
|
git -C submodule/subsub commit -m "add d" &&
|
||||||
|
git -C submodule submodule add ./subsub &&
|
||||||
|
git -C submodule commit -m "added subsub" &&
|
||||||
|
git ls-files --recurse-submodules >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--recurse-submodules does not support using path arguments' '
|
||||||
|
test_must_fail git ls-files --recurse-submodules b 2>actual &&
|
||||||
|
test_i18ngrep "does not support pathspec" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--recurse-submodules does not support --error-unmatch' '
|
||||||
|
test_must_fail git ls-files --recurse-submodules --error-unmatch 2>actual &&
|
||||||
|
test_i18ngrep "does not support --error-unmatch" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_incompatible_with_recurse_submodules () {
|
||||||
|
test_expect_success "--recurse-submodules and $1 are incompatible" "
|
||||||
|
test_must_fail git ls-files --recurse-submodules $1 2>actual &&
|
||||||
|
test_i18ngrep 'unsupported mode' actual
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_incompatible_with_recurse_submodules -z
|
||||||
|
test_incompatible_with_recurse_submodules -v
|
||||||
|
test_incompatible_with_recurse_submodules -t
|
||||||
|
test_incompatible_with_recurse_submodules --deleted
|
||||||
|
test_incompatible_with_recurse_submodules --modified
|
||||||
|
test_incompatible_with_recurse_submodules --others
|
||||||
|
test_incompatible_with_recurse_submodules --stage
|
||||||
|
test_incompatible_with_recurse_submodules --killed
|
||||||
|
test_incompatible_with_recurse_submodules --unmerged
|
||||||
|
test_incompatible_with_recurse_submodules --eol
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user