ls-files: allow relative pathspec

git ls-files used to error out if given paths which point outside the current
working directory, such as '../'. We now allow such paths and the output is
analogous to git grep -l.

Signed-off-by: Clemens Buchacher <drizzd@aon.at>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Clemens Buchacher 2010-06-03 15:39:18 +02:00 committed by Junio C Hamano
parent b167cffb6b
commit efad1a5615
2 changed files with 45 additions and 42 deletions

View File

@ -26,8 +26,9 @@ static int show_killed;
static int show_valid_bit; static int show_valid_bit;
static int line_terminator = '\n'; static int line_terminator = '\n';
static const char *prefix;
static int max_prefix_len;
static int prefix_len; static int prefix_len;
static int prefix_offset;
static const char **pathspec; static const char **pathspec;
static int error_unmatch; static int error_unmatch;
static char *ps_matched; static char *ps_matched;
@ -43,10 +44,15 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = ""; static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = ""; static const char *tag_resolve_undo = "";
static void write_name(const char* name, size_t len)
{
write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
line_terminator);
}
static void show_dir_entry(const char *tag, struct dir_entry *ent) static void show_dir_entry(const char *tag, struct dir_entry *ent)
{ {
int len = prefix_len; int len = max_prefix_len;
int offset = prefix_offset;
if (len >= ent->len) if (len >= ent->len)
die("git ls-files: internal error - directory entry not superset of prefix"); die("git ls-files: internal error - directory entry not superset of prefix");
@ -55,7 +61,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
return; return;
fputs(tag, stdout); fputs(tag, stdout);
write_name_quoted(ent->name + offset, stdout, line_terminator); write_name(ent->name, ent->len);
} }
static void show_other_files(struct dir_struct *dir) static void show_other_files(struct dir_struct *dir)
@ -121,8 +127,7 @@ static void show_killed_files(struct dir_struct *dir)
static void show_ce_entry(const char *tag, struct cache_entry *ce) static void show_ce_entry(const char *tag, struct cache_entry *ce)
{ {
int len = prefix_len; int len = max_prefix_len;
int offset = prefix_offset;
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");
@ -156,20 +161,19 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
find_unique_abbrev(ce->sha1,abbrev), find_unique_abbrev(ce->sha1,abbrev),
ce_stage(ce)); ce_stage(ce));
} }
write_name_quoted(ce->name + offset, stdout, line_terminator); write_name(ce->name, ce_namelen(ce));
} }
static int show_one_ru(struct string_list_item *item, void *cbdata) static int show_one_ru(struct string_list_item *item, void *cbdata)
{ {
int offset = prefix_offset;
const char *path = item->string; const char *path = item->string;
struct resolve_undo_info *ui = item->util; struct resolve_undo_info *ui = item->util;
int i, len; int i, len;
len = strlen(path); len = strlen(path);
if (len < prefix_len) if (len < max_prefix_len)
return 0; /* outside of the prefix */ return 0; /* outside of the prefix */
if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
return 0; /* uninterested */ return 0; /* uninterested */
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
if (!ui->mode[i]) if (!ui->mode[i])
@ -177,19 +181,19 @@ static int show_one_ru(struct string_list_item *item, void *cbdata)
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
find_unique_abbrev(ui->sha1[i], abbrev), find_unique_abbrev(ui->sha1[i], abbrev),
i + 1); i + 1);
write_name_quoted(path + offset, stdout, line_terminator); write_name(path, len);
} }
return 0; return 0;
} }
static void show_ru_info(const char *prefix) static void show_ru_info(void)
{ {
if (!the_index.resolve_undo) if (!the_index.resolve_undo)
return; return;
for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
} }
static void show_files(struct dir_struct *dir, const char *prefix) static void show_files(struct dir_struct *dir)
{ {
int i; int i;
@ -243,7 +247,7 @@ static void show_files(struct dir_struct *dir, const char *prefix)
*/ */
static void prune_cache(const char *prefix) static void prune_cache(const char *prefix)
{ {
int pos = cache_name_pos(prefix, prefix_len); int pos = cache_name_pos(prefix, max_prefix_len);
unsigned int first, last; unsigned int first, last;
if (pos < 0) if (pos < 0)
@ -256,7 +260,7 @@ static void prune_cache(const char *prefix)
while (last > first) { while (last > first) {
int next = (last + first) >> 1; int next = (last + first) >> 1;
struct cache_entry *ce = active_cache[next]; struct cache_entry *ce = active_cache[next];
if (!strncmp(ce->name, prefix, prefix_len)) { if (!strncmp(ce->name, prefix, max_prefix_len)) {
first = next+1; first = next+1;
continue; continue;
} }
@ -265,11 +269,16 @@ static void prune_cache(const char *prefix)
active_nr = last; active_nr = last;
} }
static const char *verify_pathspec(const char *prefix) static const char *pathspec_prefix(const char *prefix)
{ {
const char **p, *n, *prev; const char **p, *n, *prev;
unsigned long max; unsigned long max;
if (!pathspec) {
max_prefix_len = prefix ? strlen(prefix) : 0;
return prefix;
}
prev = NULL; prev = NULL;
max = PATH_MAX; max = PATH_MAX;
for (p = pathspec; (n = *p) != NULL; p++) { for (p = pathspec; (n = *p) != NULL; p++) {
@ -291,10 +300,7 @@ static const char *verify_pathspec(const char *prefix)
} }
} }
if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) max_prefix_len = max;
die("git ls-files: cannot generate relative filenames containing '..'");
prefix_len = max;
return max ? xmemdupz(prev, max) : NULL; return max ? xmemdupz(prev, max) : NULL;
} }
@ -374,7 +380,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
} }
} }
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len)
{ {
/* /*
* Make sure all pathspec matched; otherwise it is an error. * Make sure all pathspec matched; otherwise it is an error.
@ -404,7 +410,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
continue; continue;
error("pathspec '%s' did not match any file(s) known to git.", error("pathspec '%s' did not match any file(s) known to git.",
pathspec[num] + prefix_offset); pathspec[num] + prefix_len);
errors++; errors++;
} }
return errors; return errors;
@ -456,9 +462,10 @@ static int option_parse_exclude_standard(const struct option *opt,
return 0; return 0;
} }
int cmd_ls_files(int argc, const char **argv, const char *prefix) int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{ {
int require_work_tree = 0, show_tag = 0; int require_work_tree = 0, show_tag = 0;
const char *max_prefix;
struct dir_struct dir; struct dir_struct dir;
struct option builtin_ls_files_options[] = { struct option builtin_ls_files_options[] = {
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL, { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
@ -504,7 +511,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
"add the standard git exclusions", "add the standard git exclusions",
PARSE_OPT_NOARG, option_parse_exclude_standard }, PARSE_OPT_NOARG, option_parse_exclude_standard },
{ OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
"make the output relative to the project top directory", "make the output relative to the project top directory",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
@ -516,8 +523,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
}; };
memset(&dir, 0, sizeof(dir)); memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix) if (prefix)
prefix_offset = strlen(prefix); prefix_len = strlen(prefix);
git_config(git_default_config, NULL); git_config(git_default_config, NULL);
if (read_cache() < 0) if (read_cache() < 0)
@ -555,9 +563,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
if (pathspec) if (pathspec)
strip_trailing_slash_from_submodules(); strip_trailing_slash_from_submodules();
/* Verify that the pathspec matches the prefix */ /* Find common prefix for all pathspec's */
if (pathspec) max_prefix = pathspec_prefix(prefix);
prefix = verify_pathspec(prefix);
/* Treat unmatching pathspec elements as errors */ /* Treat unmatching pathspec elements as errors */
if (pathspec && error_unmatch) { if (pathspec && error_unmatch) {
@ -575,8 +582,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
show_killed | show_modified | show_resolve_undo)) show_killed | show_modified | show_resolve_undo))
show_cached = 1; show_cached = 1;
if (prefix) if (max_prefix)
prune_cache(prefix); prune_cache(max_prefix);
if (with_tree) { if (with_tree) {
/* /*
* Basic sanity check; show-stages and show-unmerged * Basic sanity check; show-stages and show-unmerged
@ -584,15 +591,15 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
*/ */
if (show_stage || show_unmerged) if (show_stage || show_unmerged)
die("ls-files --with-tree is incompatible with -s or -u"); die("ls-files --with-tree is incompatible with -s or -u");
overlay_tree_on_cache(with_tree, prefix); overlay_tree_on_cache(with_tree, max_prefix);
} }
show_files(&dir, prefix); show_files(&dir);
if (show_resolve_undo) if (show_resolve_undo)
show_ru_info(prefix); show_ru_info();
if (ps_matched) { if (ps_matched) {
int bad; int bad;
bad = report_path_error(ps_matched, pathspec, prefix_offset); bad = report_path_error(ps_matched, pathspec, prefix_len);
if (bad) if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n"); fprintf(stderr, "Did you forget to 'git add'?\n");

View File

@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' '
git add a && git add a &&
( (
cd a/b && cd a/b &&
if git ls-files "../e/f" git ls-files "../e/f"
then ) >current &&
echo Gaah, should have failed echo ../e/f >expect &&
exit 1 test_cmp expect current
else
: happy
fi
)
' '