X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=builtin-ls-files.c;h=dc7eab89b34fed32dbb198a9aa9a7503fc162216;hb=55029ae4dac07942437c0c715ea7c8ac60dd3576;hp=d36181a75541df10c5dc1595ccca5a6fc429591f;hpb=55d1932bce99d83fa0d891331fe3f3562d33af2e;p=git.git diff --git a/builtin-ls-files.c b/builtin-ls-files.c index d36181a75..dc7eab89b 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -9,6 +9,7 @@ #include "quote.h" #include "dir.h" #include "builtin.h" +#include "tree.h" static int abbrev; static int show_deleted; @@ -26,6 +27,7 @@ static int prefix_offset; static const char **pathspec; static int error_unmatch; static char *ps_matched; +static const char *with_tree; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -36,28 +38,28 @@ static const char *tag_modified = ""; /* - * Match a pathspec against a filename. The first "len" characters + * Match a pathspec against a filename. The first "skiplen" characters * are the common prefix */ -static int match(const char **spec, char *ps_matched, - const char *filename, int len) +int pathspec_match(const char **spec, char *ps_matched, + const char *filename, int skiplen) { const char *m; while ((m = *spec++) != NULL) { - int matchlen = strlen(m + len); + int matchlen = strlen(m + skiplen); if (!matchlen) goto matched; - if (!strncmp(m + len, filename + len, matchlen)) { - if (m[len + matchlen - 1] == '/') + if (!strncmp(m + skiplen, filename + skiplen, matchlen)) { + if (m[skiplen + matchlen - 1] == '/') goto matched; - switch (filename[len + matchlen]) { + switch (filename[skiplen + matchlen]) { case '/': case '\0': goto matched; } } - if (!fnmatch(m + len, filename + len, 0)) + if (!fnmatch(m + skiplen, filename + skiplen, 0)) goto matched; if (ps_matched) ps_matched++; @@ -78,12 +80,11 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) if (len >= ent->len) die("git-ls-files: internal error - directory entry not superset of prefix"); - if (pathspec && !match(pathspec, ps_matched, ent->name, len)) + if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len)) return; fputs(tag, stdout); - write_name_quoted("", 0, ent->name + offset, line_terminator, stdout); - putchar(line_terminator); + write_name_quoted(ent->name + offset, stdout, line_terminator); } static void show_other_files(struct dir_struct *dir) @@ -184,11 +185,11 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git-ls-files: internal error - cache entry not superset of prefix"); - if (pathspec && !match(pathspec, ps_matched, ce->name, len)) + if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len)) return; if (tag && *tag && show_valid_bit && - (ce->ce_flags & htons(CE_VALID))) { + (ce->ce_flags & CE_VALID)) { static char alttag[4]; memcpy(alttag, tag, 3); if (isalpha(tag[0])) @@ -206,21 +207,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (!show_stage) { fputs(tag, stdout); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); - } - else { + } else { printf("%s%06o %s %d\t", tag, - ntohl(ce->ce_mode), + ce->ce_mode, abbrev ? find_unique_abbrev(ce->sha1,abbrev) : sha1_to_hex(ce->sha1), ce_stage(ce)); - write_name_quoted("", 0, ce->name + offset, - line_terminator, stdout); - putchar(line_terminator); } + write_name_quoted(ce->name + offset, stdout, line_terminator); } static void show_files(struct dir_struct *dir, const char *prefix) @@ -243,10 +238,13 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (excluded(dir, ce->name) != dir->show_ignored) + int dtype = ce_to_dtype(ce); + if (excluded(dir, ce->name, &dtype) != dir->show_ignored) continue; if (show_unmerged && !ce_stage(ce)) continue; + if (ce->ce_flags & CE_UPDATE) + continue; show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); } } @@ -255,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - if (excluded(dir, ce->name) != dir->show_ignored) + int dtype = ce_to_dtype(ce); + if (excluded(dir, ce->name, &dtype) != dir->show_ignored) continue; err = lstat(ce->name, &st); if (show_deleted && err) @@ -276,7 +275,8 @@ static void prune_cache(const char *prefix) if (pos < 0) pos = -pos-1; - active_cache += pos; + memmove(active_cache, active_cache + pos, + (active_nr - pos) * sizeof(struct cache_entry *)); active_nr -= pos; first = 0; last = active_nr; @@ -295,7 +295,6 @@ static void prune_cache(const char *prefix) static const char *verify_pathspec(const char *prefix) { const char **p, *n, *prev; - char *real_prefix; unsigned long max; prev = NULL; @@ -322,21 +321,112 @@ static const char *verify_pathspec(const char *prefix) if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) die("git-ls-files: cannot generate relative filenames containing '..'"); - real_prefix = NULL; prefix_len = max; - if (max) { - real_prefix = xmalloc(max + 1); - memcpy(real_prefix, prev, max); - real_prefix[max] = 0; + return max ? xmemdupz(prev, max) : NULL; +} + +/* + * Read the tree specified with --with-tree option + * (typically, HEAD) into stage #1 and then + * squash them down to stage #0. This is used for + * --error-unmatch to list and check the path patterns + * that were given from the command line. We are not + * going to write this index out. + */ +void overlay_tree_on_cache(const char *tree_name, const char *prefix) +{ + struct tree *tree; + unsigned char sha1[20]; + const char **match; + struct cache_entry *last_stage0 = NULL; + int i; + + if (get_sha1(tree_name, sha1)) + die("tree-ish %s not found.", tree_name); + tree = parse_tree_indirect(sha1); + if (!tree) + die("bad tree-ish %s", tree_name); + + /* Hoist the unmerged entries up to stage #3 to make room */ + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + ce->ce_flags |= CE_STAGEMASK; + } + + if (prefix) { + static const char *(matchbuf[2]); + matchbuf[0] = prefix; + matchbuf [1] = NULL; + match = matchbuf; + } else + match = NULL; + if (read_tree(tree, 1, match)) + die("unable to read tree entries %s", tree_name); + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + switch (ce_stage(ce)) { + case 0: + last_stage0 = ce; + /* fallthru */ + default: + continue; + case 1: + /* + * If there is stage #0 entry for this, we do not + * need to show it. We use CE_UPDATE bit to mark + * such an entry. + */ + if (last_stage0 && + !strcmp(last_stage0->name, ce->name)) + ce->ce_flags |= CE_UPDATE; + } + } +} + +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) +{ + /* + * Make sure all pathspec matched; otherwise it is an error. + */ + int num, errors = 0; + for (num = 0; pathspec[num]; num++) { + int other, found_dup; + + if (ps_matched[num]) + continue; + /* + * The caller might have fed identical pathspec + * twice. Do not barf on such a mistake. + */ + for (found_dup = other = 0; + !found_dup && pathspec[other]; + other++) { + if (other == num || !ps_matched[other]) + continue; + if (!strcmp(pathspec[other], pathspec[num])) + /* + * Ok, we have a match already. + */ + found_dup = 1; + } + if (found_dup) + continue; + + error("pathspec '%s' did not match any file(s) known to git.", + pathspec[num] + prefix_offset); + errors++; } - return real_prefix; + return errors; } static const char ls_files_usage[] = "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=] [--exclude-from=] " - "[ --exclude-per-directory= ] [--full-name] [--abbrev] " - "[--] []*"; + "[ --exclude-per-directory= ] [--exclude-standard] " + "[--full-name] [--abbrev] [--] []*"; int cmd_ls_files(int argc, const char **argv, const char *prefix) { @@ -444,6 +534,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) dir.exclude_per_dir = arg + 24; continue; } + if (!strcmp(arg, "--exclude-standard")) { + exc_given = 1; + setup_standard_excludes(&dir); + continue; + } if (!strcmp(arg, "--full-name")) { prefix_offset = 0; continue; @@ -452,6 +547,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) error_unmatch = 1; continue; } + if (!prefixcmp(arg, "--with-tree=")) { + with_tree = arg + 12; + continue; + } if (!prefixcmp(arg, "--abbrev=")) { abbrev = strtoul(arg+9, NULL, 10); if (abbrev && abbrev < MINIMUM_ABBREV) @@ -469,11 +568,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) break; } - if (require_work_tree && !is_inside_work_tree()) { - const char *work_tree = get_git_work_tree(); - if (!work_tree || chdir(work_tree)) - die("This operation must be run in a work tree"); - } + if (require_work_tree && !is_inside_work_tree()) + setup_work_tree(); pathspec = get_pathspec(prefix, argv + i); @@ -503,25 +599,24 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) read_cache(); if (prefix) prune_cache(prefix); + if (with_tree) { + /* + * Basic sanity check; show-stages and show-unmerged + * would not make any sense with this option. + */ + if (show_stage || show_unmerged) + die("ls-files --with-tree is incompatible with -s or -u"); + overlay_tree_on_cache(with_tree, prefix); + } show_files(&dir, prefix); if (ps_matched) { - /* We need to make sure all pathspec matched otherwise - * it is an error. - */ - int num, errors = 0; - for (num = 0; pathspec[num]; num++) { - if (ps_matched[num]) - continue; - error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); - errors++; - } - - if (errors) + int bad; + bad = report_path_error(ps_matched, pathspec, prefix_offset); + if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); - return errors ? 1 : 0; + return bad ? 1 : 0; } return 0;