Skip to content

Commit efad1a5

Browse files
Clemens Buchachergitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b167cff commit efad1a5

File tree

2 files changed

+45
-42
lines changed

2 files changed

+45
-42
lines changed

builtin/ls-files.c

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ static int show_killed;
2626
static int show_valid_bit;
2727
static int line_terminator = '\n';
2828

29+
static const char *prefix;
30+
static int max_prefix_len;
2931
static int prefix_len;
30-
static int prefix_offset;
3132
static const char **pathspec;
3233
static int error_unmatch;
3334
static char *ps_matched;
@@ -43,10 +44,15 @@ static const char *tag_modified = "";
4344
static const char *tag_skip_worktree = "";
4445
static const char *tag_resolve_undo = "";
4546

47+
static void write_name(const char* name, size_t len)
48+
{
49+
write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
50+
line_terminator);
51+
}
52+
4653
static void show_dir_entry(const char *tag, struct dir_entry *ent)
4754
{
48-
int len = prefix_len;
49-
int offset = prefix_offset;
55+
int len = max_prefix_len;
5056

5157
if (len >= ent->len)
5258
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)
5561
return;
5662

5763
fputs(tag, stdout);
58-
write_name_quoted(ent->name + offset, stdout, line_terminator);
64+
write_name(ent->name, ent->len);
5965
}
6066

6167
static void show_other_files(struct dir_struct *dir)
@@ -121,8 +127,7 @@ static void show_killed_files(struct dir_struct *dir)
121127

122128
static void show_ce_entry(const char *tag, struct cache_entry *ce)
123129
{
124-
int len = prefix_len;
125-
int offset = prefix_offset;
130+
int len = max_prefix_len;
126131

127132
if (len >= ce_namelen(ce))
128133
die("git ls-files: internal error - cache entry not superset of prefix");
@@ -156,40 +161,39 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
156161
find_unique_abbrev(ce->sha1,abbrev),
157162
ce_stage(ce));
158163
}
159-
write_name_quoted(ce->name + offset, stdout, line_terminator);
164+
write_name(ce->name, ce_namelen(ce));
160165
}
161166

162167
static int show_one_ru(struct string_list_item *item, void *cbdata)
163168
{
164-
int offset = prefix_offset;
165169
const char *path = item->string;
166170
struct resolve_undo_info *ui = item->util;
167171
int i, len;
168172

169173
len = strlen(path);
170-
if (len < prefix_len)
174+
if (len < max_prefix_len)
171175
return 0; /* outside of the prefix */
172-
if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
176+
if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
173177
return 0; /* uninterested */
174178
for (i = 0; i < 3; i++) {
175179
if (!ui->mode[i])
176180
continue;
177181
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
178182
find_unique_abbrev(ui->sha1[i], abbrev),
179183
i + 1);
180-
write_name_quoted(path + offset, stdout, line_terminator);
184+
write_name(path, len);
181185
}
182186
return 0;
183187
}
184188

185-
static void show_ru_info(const char *prefix)
189+
static void show_ru_info(void)
186190
{
187191
if (!the_index.resolve_undo)
188192
return;
189193
for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
190194
}
191195

192-
static void show_files(struct dir_struct *dir, const char *prefix)
196+
static void show_files(struct dir_struct *dir)
193197
{
194198
int i;
195199

@@ -243,7 +247,7 @@ static void show_files(struct dir_struct *dir, const char *prefix)
243247
*/
244248
static void prune_cache(const char *prefix)
245249
{
246-
int pos = cache_name_pos(prefix, prefix_len);
250+
int pos = cache_name_pos(prefix, max_prefix_len);
247251
unsigned int first, last;
248252

249253
if (pos < 0)
@@ -256,7 +260,7 @@ static void prune_cache(const char *prefix)
256260
while (last > first) {
257261
int next = (last + first) >> 1;
258262
struct cache_entry *ce = active_cache[next];
259-
if (!strncmp(ce->name, prefix, prefix_len)) {
263+
if (!strncmp(ce->name, prefix, max_prefix_len)) {
260264
first = next+1;
261265
continue;
262266
}
@@ -265,11 +269,16 @@ static void prune_cache(const char *prefix)
265269
active_nr = last;
266270
}
267271

268-
static const char *verify_pathspec(const char *prefix)
272+
static const char *pathspec_prefix(const char *prefix)
269273
{
270274
const char **p, *n, *prev;
271275
unsigned long max;
272276

277+
if (!pathspec) {
278+
max_prefix_len = prefix ? strlen(prefix) : 0;
279+
return prefix;
280+
}
281+
273282
prev = NULL;
274283
max = PATH_MAX;
275284
for (p = pathspec; (n = *p) != NULL; p++) {
@@ -291,10 +300,7 @@ static const char *verify_pathspec(const char *prefix)
291300
}
292301
}
293302

294-
if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
295-
die("git ls-files: cannot generate relative filenames containing '..'");
296-
297-
prefix_len = max;
303+
max_prefix_len = max;
298304
return max ? xmemdupz(prev, max) : NULL;
299305
}
300306

@@ -374,7 +380,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
374380
}
375381
}
376382

377-
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
383+
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len)
378384
{
379385
/*
380386
* 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_
404410
continue;
405411

406412
error("pathspec '%s' did not match any file(s) known to git.",
407-
pathspec[num] + prefix_offset);
413+
pathspec[num] + prefix_len);
408414
errors++;
409415
}
410416
return errors;
@@ -456,9 +462,10 @@ static int option_parse_exclude_standard(const struct option *opt,
456462
return 0;
457463
}
458464

459-
int cmd_ls_files(int argc, const char **argv, const char *prefix)
465+
int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
460466
{
461467
int require_work_tree = 0, show_tag = 0;
468+
const char *max_prefix;
462469
struct dir_struct dir;
463470
struct option builtin_ls_files_options[] = {
464471
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
@@ -504,7 +511,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
504511
{ OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
505512
"add the standard git exclusions",
506513
PARSE_OPT_NOARG, option_parse_exclude_standard },
507-
{ OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
514+
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
508515
"make the output relative to the project top directory",
509516
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
510517
OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
@@ -516,8 +523,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
516523
};
517524

518525
memset(&dir, 0, sizeof(dir));
526+
prefix = cmd_prefix;
519527
if (prefix)
520-
prefix_offset = strlen(prefix);
528+
prefix_len = strlen(prefix);
521529
git_config(git_default_config, NULL);
522530

523531
if (read_cache() < 0)
@@ -555,9 +563,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
555563
if (pathspec)
556564
strip_trailing_slash_from_submodules();
557565

558-
/* Verify that the pathspec matches the prefix */
559-
if (pathspec)
560-
prefix = verify_pathspec(prefix);
566+
/* Find common prefix for all pathspec's */
567+
max_prefix = pathspec_prefix(prefix);
561568

562569
/* Treat unmatching pathspec elements as errors */
563570
if (pathspec && error_unmatch) {
@@ -575,24 +582,24 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
575582
show_killed | show_modified | show_resolve_undo))
576583
show_cached = 1;
577584

578-
if (prefix)
579-
prune_cache(prefix);
585+
if (max_prefix)
586+
prune_cache(max_prefix);
580587
if (with_tree) {
581588
/*
582589
* Basic sanity check; show-stages and show-unmerged
583590
* would not make any sense with this option.
584591
*/
585592
if (show_stage || show_unmerged)
586593
die("ls-files --with-tree is incompatible with -s or -u");
587-
overlay_tree_on_cache(with_tree, prefix);
594+
overlay_tree_on_cache(with_tree, max_prefix);
588595
}
589-
show_files(&dir, prefix);
596+
show_files(&dir);
590597
if (show_resolve_undo)
591-
show_ru_info(prefix);
598+
show_ru_info();
592599

593600
if (ps_matched) {
594601
int bad;
595-
bad = report_path_error(ps_matched, pathspec, prefix_offset);
602+
bad = report_path_error(ps_matched, pathspec, prefix_len);
596603
if (bad)
597604
fprintf(stderr, "Did you forget to 'git add'?\n");
598605

t/t7010-setup.sh

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' '
103103
git add a &&
104104
(
105105
cd a/b &&
106-
if git ls-files "../e/f"
107-
then
108-
echo Gaah, should have failed
109-
exit 1
110-
else
111-
: happy
112-
fi
113-
)
106+
git ls-files "../e/f"
107+
) >current &&
108+
echo ../e/f >expect &&
109+
test_cmp expect current
114110
115111
'
116112

0 commit comments

Comments
 (0)