From b167cffb6b38a092e8f9e0e396c1931ee1dc8b7d Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Thu, 3 Jun 2010 15:36:31 +0200 Subject: [PATCH 1/2] quote.c: separate quoting and relative path generation This is in preparation of relative path support for ls-files, which quotes a path only if the line terminator is not the NUL character. Signed-off-by: Clemens Buchacher Signed-off-by: Junio C Hamano --- quote.c | 95 ++++++++++++++++++++++++++++++++++++++------------------- quote.h | 5 ++- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/quote.c b/quote.c index fc93435727..63d3b01818 100644 --- a/quote.c +++ b/quote.c @@ -295,42 +295,75 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen, fputc(terminator, fp); } +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len); + +void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator) +{ + struct strbuf sb = STRBUF_INIT; + + name = path_relative(name, len, &sb, prefix, prefix_len); + write_name_quoted(name, fp, terminator); + + strbuf_release(&sb); +} + +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +static const char *path_relative(const char *in, int len, + struct strbuf *sb, const char *prefix, + int prefix_len) +{ + int off, i; + + if (len < 0) + len = strlen(in); + if (prefix && prefix_len < 0) + prefix_len = strlen(prefix); + + off = 0; + i = 0; + while (i < prefix_len && i < len && prefix[i] == in[i]) { + if (prefix[i] == '/') + off = i + 1; + i++; + } + in += off; + len -= off; + + if (i >= prefix_len) + return in; + + strbuf_reset(sb); + strbuf_grow(sb, len); + + while (i < prefix_len) { + if (prefix[i] == '/') + strbuf_addstr(sb, "../"); + i++; + } + strbuf_add(sb, in, len); + + return sb->buf; +} + /* quote path as relative to the given prefix */ char *quote_path_relative(const char *in, int len, struct strbuf *out, const char *prefix) { - int needquote; + struct strbuf sb = STRBUF_INIT; + const char *rel = path_relative(in, len, &sb, prefix, -1); + strbuf_reset(out); + quote_c_style_counted(rel, strlen(rel), out, NULL, 0); + strbuf_release(&sb); - if (len < 0) - len = strlen(in); - - /* "../" prefix itself does not need quoting, but "in" might. */ - needquote = next_quote_pos(in, len) < len; - strbuf_setlen(out, 0); - strbuf_grow(out, len); - - if (needquote) - strbuf_addch(out, '"'); - if (prefix) { - int off = 0; - while (prefix[off] && off < len && prefix[off] == in[off]) - if (prefix[off] == '/') { - prefix += off + 1; - in += off + 1; - len -= off + 1; - off = 0; - } else - off++; - - for (; *prefix; prefix++) - if (*prefix == '/') - strbuf_addstr(out, "../"); - } - - quote_c_style_counted (in, len, out, NULL, 1); - - if (needquote) - strbuf_addch(out, '"'); if (!out->len) strbuf_addstr(out, "./"); diff --git a/quote.h b/quote.h index f83eb233c4..38003bff5f 100644 --- a/quote.h +++ b/quote.h @@ -54,9 +54,12 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); extern void write_name_quoted(const char *name, FILE *, int terminator); extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, const char *name, FILE *, int terminator); +extern void write_name_quoted_relative(const char *name, size_t len, + const char *prefix, size_t prefix_len, + FILE *fp, int terminator); /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, +extern char *quote_path_relative(const char *in, int len, struct strbuf *out, const char *prefix); /* quoting as a string literal for other languages */ From efad1a5615e80637dcb38c8eaa1e35c03b24169f Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Thu, 3 Jun 2010 15:39:18 +0200 Subject: [PATCH 2/2] 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 Signed-off-by: Junio C Hamano --- builtin/ls-files.c | 75 +++++++++++++++++++++++++--------------------- t/t7010-setup.sh | 12 +++----- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c0fbcdcf4f..0804047693 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -26,8 +26,9 @@ static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; +static const char *prefix; +static int max_prefix_len; static int prefix_len; -static int prefix_offset; static const char **pathspec; static int error_unmatch; static char *ps_matched; @@ -43,10 +44,15 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; 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) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ent->len) 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; 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) @@ -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) { - int len = prefix_len; - int offset = prefix_offset; + int len = max_prefix_len; if (len >= ce_namelen(ce)) 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), 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) { - int offset = prefix_offset; const char *path = item->string; struct resolve_undo_info *ui = item->util; int i, len; len = strlen(path); - if (len < prefix_len) + if (len < max_prefix_len) 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 */ for (i = 0; i < 3; 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], find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name_quoted(path + offset, stdout, line_terminator); + write_name(path, len); } return 0; } -static void show_ru_info(const char *prefix) +static void show_ru_info(void) { if (!the_index.resolve_undo) return; 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; @@ -243,7 +247,7 @@ static void show_files(struct dir_struct *dir, 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; if (pos < 0) @@ -256,7 +260,7 @@ static void prune_cache(const char *prefix) while (last > first) { int next = (last + first) >> 1; 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; continue; } @@ -265,11 +269,16 @@ static void prune_cache(const char *prefix) active_nr = last; } -static const char *verify_pathspec(const char *prefix) +static const char *pathspec_prefix(const char *prefix) { const char **p, *n, *prev; unsigned long max; + if (!pathspec) { + max_prefix_len = prefix ? strlen(prefix) : 0; + return prefix; + } + prev = NULL; max = PATH_MAX; 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)) - die("git ls-files: cannot generate relative filenames containing '..'"); - - prefix_len = max; + max_prefix_len = max; 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. @@ -404,7 +410,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ continue; error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); + pathspec[num] + prefix_len); errors++; } return errors; @@ -456,9 +462,10 @@ static int option_parse_exclude_standard(const struct option *opt, 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; + const char *max_prefix; struct dir_struct dir; struct option builtin_ls_files_options[] = { { 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, "add the standard git exclusions", 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", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, 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)); + prefix = cmd_prefix; if (prefix) - prefix_offset = strlen(prefix); + prefix_len = strlen(prefix); git_config(git_default_config, NULL); if (read_cache() < 0) @@ -555,9 +563,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) if (pathspec) strip_trailing_slash_from_submodules(); - /* Verify that the pathspec matches the prefix */ - if (pathspec) - prefix = verify_pathspec(prefix); + /* Find common prefix for all pathspec's */ + max_prefix = pathspec_prefix(prefix); /* Treat unmatching pathspec elements as errors */ 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_cached = 1; - if (prefix) - prune_cache(prefix); + if (max_prefix) + prune_cache(max_prefix); if (with_tree) { /* * 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) 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) - show_ru_info(prefix); + show_ru_info(); if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix_offset); + bad = report_path_error(ps_matched, pathspec, prefix_len); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/t/t7010-setup.sh b/t/t7010-setup.sh index d8a7c79852..0335a9a158 100755 --- a/t/t7010-setup.sh +++ b/t/t7010-setup.sh @@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' ' git add a && ( cd a/b && - if git ls-files "../e/f" - then - echo Gaah, should have failed - exit 1 - else - : happy - fi - ) + git ls-files "../e/f" + ) >current && + echo ../e/f >expect && + test_cmp expect current '