Skip to content

Commit 5be4efb

Browse files
Linus TorvaldsJunio C Hamano
authored andcommitted
[PATCH] Make "git-ls-files" work in subdirectories
This makes git-ls-files work inside a relative directory, and also adds some rudimentary filename globbing support. For example, in the kernel you can now do cd arch/i386 git-ls-files and it will show all files under that subdirectory (and it will have removed the "arch/i386/" prefix unless you give it the "--full-name" option, so that you can feed the result to "xargs grep" or similar). The filename globbing is kind of strange: it does _not_ follow normal globbing rules, although it does look "almost" like a normal file glob (and it uses the POSIX.2 "fnmatch()" function). The glob pattern (there can be only one) is always split into a "directory part" and a "glob part", where the directory part is defined as any full directory path without any '*' or '?' characters. The "glob" part is whatever is left over. For example, when doing git-ls-files 'arch/i386/p*/*.c' the "directory part" is is "arch/i386/", and the "glob part" is "p*/*.c". The directory part will be added to the prefix, and handled efficiently (ie we will not be searching outside of that subdirectory), while the glob part (if anything is left over) will be used to trigger "fnmatch()" matches. This is efficient and very useful, but can result in somewhat non-intuitive behaviour. For example: git-ls-files 'arch/i386/*.[ch]' will find all .c and .h files under arch/i386/, _including_ things in lower subdirectories (ie it will match "arch/i386/kernel/process.c", because "kernel/process.c" will match the "*.c" specifier). Also, while git-ls-files arch/i386/ will show all files under that subdirectory, doing the same without the final slash would try to show the file "i386" under the "arch/" subdirectory, and since there is no such file (even if there is such a _directory_) it will not match anything at all. These semantics may not seem intuitive, but they are actually very practical. In particular, it makes it very simple to do git-ls-files fs/*.c | xargs grep some_pattern and it does what you want. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent 1dfcfbc commit 5be4efb

File tree

1 file changed

+165
-36
lines changed

1 file changed

+165
-36
lines changed

ls-files.c

Lines changed: 165 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ static int show_unmerged = 0;
1919
static int show_killed = 0;
2020
static int line_terminator = '\n';
2121

22+
static int prefix_len = 0, prefix_offset = 0;
23+
static const char *prefix = NULL;
24+
static const char *glob = NULL;
25+
2226
static const char *tag_cached = "";
2327
static const char *tag_unmerged = "";
2428
static const char *tag_removed = "";
@@ -222,6 +226,7 @@ static void add_name(const char *pathname, int len)
222226
ent = xmalloc(sizeof(*ent) + len + 1);
223227
ent->len = len;
224228
memcpy(ent->name, pathname, len);
229+
ent->name[len] = 0;
225230
dir[nr_dir++] = ent;
226231
}
227232

@@ -297,6 +302,20 @@ static int cmp_name(const void *p1, const void *p2)
297302
e2->name, e2->len);
298303
}
299304

305+
static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
306+
{
307+
int len = prefix_len;
308+
int offset = prefix_offset;
309+
310+
if (len >= ent->len)
311+
die("git-ls-files: internal error - directory entry not superset of prefix");
312+
313+
if (glob && fnmatch(glob, ent->name + len, 0))
314+
return;
315+
316+
printf("%s%s%c", tag, ent->name + offset, line_terminator);
317+
}
318+
300319
static void show_killed_files(void)
301320
{
302321
int i;
@@ -342,25 +361,48 @@ static void show_killed_files(void)
342361
}
343362
}
344363
if (killed)
345-
printf("%s%.*s%c", tag_killed,
346-
dir[i]->len, dir[i]->name,
347-
line_terminator);
364+
show_dir_entry(tag_killed, dir[i]);
348365
}
349366
}
350367

368+
static void show_ce_entry(const char *tag, struct cache_entry *ce)
369+
{
370+
int len = prefix_len;
371+
int offset = prefix_offset;
372+
373+
if (len >= ce_namelen(ce))
374+
die("git-ls-files: internal error - cache entry not superset of prefix");
375+
376+
if (glob && fnmatch(glob, ce->name + len, 0))
377+
return;
378+
379+
if (!show_stage)
380+
printf("%s%s%c", tag, ce->name + offset, line_terminator);
381+
else
382+
printf("%s%06o %s %d\t%s%c",
383+
tag,
384+
ntohl(ce->ce_mode),
385+
sha1_to_hex(ce->sha1),
386+
ce_stage(ce),
387+
ce->name + offset, line_terminator);
388+
}
389+
351390
static void show_files(void)
352391
{
353392
int i;
354393

355394
/* For cached/deleted files we don't need to even do the readdir */
356395
if (show_others || show_killed) {
357-
read_directory(".", "", 0);
396+
const char *path = ".", *base = "";
397+
int baselen = prefix_len;
398+
399+
if (baselen)
400+
path = base = prefix;
401+
read_directory(path, base, baselen);
358402
qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
359403
if (show_others)
360404
for (i = 0; i < nr_dir; i++)
361-
printf("%s%.*s%c", tag_other,
362-
dir[i]->len, dir[i]->name,
363-
line_terminator);
405+
show_dir_entry(tag_other, dir[i]);
364406
if (show_killed)
365407
show_killed_files();
366408
}
@@ -371,19 +413,7 @@ static void show_files(void)
371413
continue;
372414
if (show_unmerged && !ce_stage(ce))
373415
continue;
374-
if (!show_stage)
375-
printf("%s%s%c",
376-
ce_stage(ce) ? tag_unmerged :
377-
tag_cached,
378-
ce->name, line_terminator);
379-
else
380-
printf("%s%06o %s %d\t%s%c",
381-
ce_stage(ce) ? tag_unmerged :
382-
tag_cached,
383-
ntohl(ce->ce_mode),
384-
sha1_to_hex(ce->sha1),
385-
ce_stage(ce),
386-
ce->name, line_terminator);
416+
show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
387417
}
388418
}
389419
if (show_deleted) {
@@ -394,9 +424,66 @@ static void show_files(void)
394424
continue;
395425
if (!lstat(ce->name, &st))
396426
continue;
397-
printf("%s%s%c", tag_removed, ce->name,
398-
line_terminator);
427+
show_ce_entry(tag_removed, ce);
428+
}
429+
}
430+
}
431+
432+
/*
433+
* Prune the index to only contain stuff starting with "prefix"
434+
*/
435+
static void prune_cache(void)
436+
{
437+
int pos = cache_name_pos(prefix, prefix_len);
438+
unsigned int first, last;
439+
440+
if (pos < 0)
441+
pos = -pos-1;
442+
active_cache += pos;
443+
active_nr -= pos;
444+
first = 0;
445+
last = active_nr;
446+
while (last > first) {
447+
int next = (last + first) >> 1;
448+
struct cache_entry *ce = active_cache[next];
449+
if (!strncmp(ce->name, prefix, prefix_len)) {
450+
first = next+1;
451+
continue;
399452
}
453+
last = next;
454+
}
455+
active_nr = last;
456+
}
457+
458+
/*
459+
* If the glob starts with a subdirectory, append it to
460+
* the prefix instead, for more efficient operation.
461+
*
462+
* But we do not update the "prefix_offset", which tells
463+
* how much of the name to ignore at printout.
464+
*/
465+
static void extend_prefix(void)
466+
{
467+
const char *p, *slash;
468+
char c;
469+
470+
p = glob;
471+
slash = NULL;
472+
while ((c = *p++) != '\0') {
473+
if (c == '*')
474+
break;
475+
if (c == '/')
476+
slash = p;
477+
}
478+
if (slash) {
479+
int len = slash - glob;
480+
char *newprefix = xmalloc(len + prefix_len + 1);
481+
memcpy(newprefix, prefix, prefix_len);
482+
memcpy(newprefix + prefix_len, glob, len);
483+
prefix_len += len;
484+
newprefix[prefix_len] = 0;
485+
prefix = newprefix;
486+
glob = *slash ? slash : NULL;
400487
}
401488
}
402489

@@ -410,54 +497,94 @@ int main(int argc, char **argv)
410497
int i;
411498
int exc_given = 0;
412499

500+
prefix = setup_git_directory();
501+
if (prefix)
502+
prefix_offset = prefix_len = strlen(prefix);
503+
413504
for (i = 1; i < argc; i++) {
414505
char *arg = argv[i];
415506

416507
if (!strcmp(arg, "-z")) {
417508
line_terminator = 0;
418-
} else if (!strcmp(arg, "-t")) {
509+
continue;
510+
}
511+
if (!strcmp(arg, "-t")) {
419512
tag_cached = "H ";
420513
tag_unmerged = "M ";
421514
tag_removed = "R ";
422515
tag_other = "? ";
423516
tag_killed = "K ";
424-
} else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
517+
continue;
518+
}
519+
if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
425520
show_cached = 1;
426-
} else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
521+
continue;
522+
}
523+
if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
427524
show_deleted = 1;
428-
} else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
525+
continue;
526+
}
527+
if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
429528
show_others = 1;
430-
} else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
529+
continue;
530+
}
531+
if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
431532
show_ignored = 1;
432-
} else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
533+
continue;
534+
}
535+
if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
433536
show_stage = 1;
434-
} else if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
537+
continue;
538+
}
539+
if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
435540
show_killed = 1;
436-
} else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
541+
continue;
542+
}
543+
if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
437544
/* There's no point in showing unmerged unless
438545
* you also show the stage information.
439546
*/
440547
show_stage = 1;
441548
show_unmerged = 1;
442-
} else if (!strcmp(arg, "-x") && i+1 < argc) {
549+
continue;
550+
}
551+
if (!strcmp(arg, "-x") && i+1 < argc) {
443552
exc_given = 1;
444553
add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
445-
} else if (!strncmp(arg, "--exclude=", 10)) {
554+
continue;
555+
}
556+
if (!strncmp(arg, "--exclude=", 10)) {
446557
exc_given = 1;
447558
add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
448-
} else if (!strcmp(arg, "-X") && i+1 < argc) {
559+
continue;
560+
}
561+
if (!strcmp(arg, "-X") && i+1 < argc) {
449562
exc_given = 1;
450563
add_excludes_from_file(argv[++i]);
451-
} else if (!strncmp(arg, "--exclude-from=", 15)) {
564+
continue;
565+
}
566+
if (!strncmp(arg, "--exclude-from=", 15)) {
452567
exc_given = 1;
453568
add_excludes_from_file(arg+15);
454-
} else if (!strncmp(arg, "--exclude-per-directory=", 24)) {
569+
continue;
570+
}
571+
if (!strncmp(arg, "--exclude-per-directory=", 24)) {
455572
exc_given = 1;
456573
exclude_per_dir = arg + 24;
457-
} else
574+
continue;
575+
}
576+
if (!strcmp(arg, "--full-name")) {
577+
prefix_offset = 0;
578+
continue;
579+
}
580+
if (glob || *arg == '-')
458581
usage(ls_files_usage);
582+
glob = arg;
459583
}
460584

585+
if (glob)
586+
extend_prefix();
587+
461588
if (show_ignored && !exc_given) {
462589
fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
463590
argv[0]);
@@ -469,6 +596,8 @@ int main(int argc, char **argv)
469596
show_cached = 1;
470597

471598
read_cache();
599+
if (prefix)
600+
prune_cache();
472601
show_files();
473602
return 0;
474603
}

0 commit comments

Comments
 (0)