diff options
Diffstat (limited to 'tree-walk.c')
-rw-r--r-- | tree-walk.c | 340 |
1 files changed, 322 insertions, 18 deletions
diff --git a/tree-walk.c b/tree-walk.c index 5dd9a71804..ff77605680 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -22,31 +22,60 @@ static const char *get_mode(const char *str, unsigned int *modep) return str; } -static void decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size) +static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err) { const char *path; unsigned int mode, len; - if (size < 24 || buf[size - 21]) - die("corrupt tree file"); + if (size < 23 || buf[size - 21]) { + strbuf_addstr(err, _("too-short tree object")); + return -1; + } path = get_mode(buf, &mode); - if (!path || !*path) - die("corrupt tree file"); + if (!path) { + strbuf_addstr(err, _("malformed mode in tree entry")); + return -1; + } + if (!*path) { + strbuf_addstr(err, _("empty filename in tree entry")); + return -1; + } len = strlen(path) + 1; /* Initialize the descriptor entry */ desc->entry.path = path; desc->entry.mode = canon_mode(mode); - desc->entry.sha1 = (const unsigned char *)(path + len); + desc->entry.oid = (const struct object_id *)(path + len); + + return 0; } -void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size) +static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, unsigned long size, struct strbuf *err) { desc->buffer = buffer; desc->size = size; if (size) - decode_tree_entry(desc, buffer, size); + return decode_tree_entry(desc, buffer, size, err); + return 0; +} + +void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size) +{ + struct strbuf err = STRBUF_INIT; + if (init_tree_desc_internal(desc, buffer, size, &err)) + die("%s", err.buf); + strbuf_release(&err); +} + +int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size) +{ + struct strbuf err = STRBUF_INIT; + int result = init_tree_desc_internal(desc, buffer, size, &err); + if (result) + error("%s", err.buf); + strbuf_release(&err); + return result; } void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1) @@ -73,21 +102,44 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a) *a = t->entry; } -void update_tree_entry(struct tree_desc *desc) +static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err) { const void *buf = desc->buffer; - const unsigned char *end = desc->entry.sha1 + 20; + const unsigned char *end = desc->entry.oid->hash + 20; unsigned long size = desc->size; unsigned long len = end - (const unsigned char *)buf; if (size < len) - die("corrupt tree file"); + die(_("too-short tree file")); buf = end; size -= len; desc->buffer = buf; desc->size = size; if (size) - decode_tree_entry(desc, buf, size); + return decode_tree_entry(desc, buf, size, err); + return 0; +} + +void update_tree_entry(struct tree_desc *desc) +{ + struct strbuf err = STRBUF_INIT; + if (update_tree_entry_internal(desc, &err)) + die("%s", err.buf); + strbuf_release(&err); +} + +int update_tree_entry_gently(struct tree_desc *desc) +{ + struct strbuf err = STRBUF_INIT; + if (update_tree_entry_internal(desc, &err)) { + error("%s", err.buf); + strbuf_release(&err); + /* Stop processing this tree after error */ + desc->size = 0; + return -1; + } + strbuf_release(&err); + return 0; } int tree_entry(struct tree_desc *desc, struct name_entry *entry) @@ -100,6 +152,17 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry) return 1; } +int tree_entry_gently(struct tree_desc *desc, struct name_entry *entry) +{ + if (!desc->size) + return 0; + + *entry = desc->entry; + if (update_tree_entry_gently(desc)) + return 0; + return 1; +} + void setup_traverse_info(struct traverse_info *info, const char *base) { int pathlen = strlen(base); @@ -110,7 +173,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base) pathlen--; info->pathlen = pathlen ? pathlen + 1 : 0; info->name.path = base; - info->name.sha1 = (void *)(base + pathlen + 1); + info->name.oid = (void *)(base + pathlen + 1); if (pathlen) info->prev = &dummy; } @@ -320,6 +383,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) struct tree_desc_x *tx = xcalloc(n, sizeof(*tx)); struct strbuf base = STRBUF_INIT; int interesting = 1; + char *traverse_path; for (i = 0; i < n; i++) tx[i].d = t[i]; @@ -329,7 +393,11 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) make_traverse_path(base.buf, info->prev, &info->name); base.buf[info->pathlen-1] = '/'; strbuf_setlen(&base, info->pathlen); + traverse_path = xstrndup(base.buf, info->pathlen); + } else { + traverse_path = xstrndup(info->name.path, info->pathlen); } + info->traverse_path = traverse_path; for (;;) { int trees_used; unsigned long mask, dirmask; @@ -411,19 +479,27 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) for (i = 0; i < n; i++) free_extended_entry(tx + i); free(tx); + free(traverse_path); + info->traverse_path = NULL; strbuf_release(&base); return error; } +struct dir_state { + void *tree; + unsigned long size; + unsigned char sha1[20]; +}; + static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) { int namelen = strlen(name); while (t->size) { const char *entry; - const unsigned char *sha1; + const struct object_id *oid; int entrylen, cmp; - sha1 = tree_entry_extract(t, &entry, mode); + oid = tree_entry_extract(t, &entry, mode); entrylen = tree_entry_len(&t->entry); update_tree_entry(t); if (entrylen > namelen) @@ -434,7 +510,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (cmp < 0) break; if (entrylen == namelen) { - hashcpy(result, sha1); + hashcpy(result, oid->hash); return 0; } if (name[entrylen] != '/') @@ -442,10 +518,10 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { - hashcpy(result, sha1); + hashcpy(result, oid->hash); return 0; } - return get_tree_entry(sha1, name + entrylen, result, mode); + return get_tree_entry(oid->hash, name + entrylen, result, mode); } return -1; } @@ -478,6 +554,206 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch return retval; } +/* + * This is Linux's built-in max for the number of symlinks to follow. + * That limit, of course, does not affect git, but it's a reasonable + * choice. + */ +#define GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS 40 + +/** + * Find a tree entry by following symlinks in tree_sha (which is + * assumed to be the root of the repository). In the event that a + * symlink points outside the repository (e.g. a link to /foo or a + * root-level link to ../foo), the portion of the link which is + * outside the repository will be returned in result_path, and *mode + * will be set to 0. It is assumed that result_path is uninitialized. + * If there are no symlinks, or the end result of the symlink chain + * points to an object inside the repository, result will be filled in + * with the sha1 of the found object, and *mode will hold the mode of + * the object. + * + * See the code for enum follow_symlink_result for a description of + * the return values. + */ +enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode) +{ + int retval = MISSING_OBJECT; + struct dir_state *parents = NULL; + size_t parents_alloc = 0; + ssize_t parents_nr = 0; + unsigned char current_tree_sha1[20]; + struct strbuf namebuf = STRBUF_INIT; + struct tree_desc t; + int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS; + int i; + + init_tree_desc(&t, NULL, 0UL); + strbuf_init(result_path, 0); + strbuf_addstr(&namebuf, name); + hashcpy(current_tree_sha1, tree_sha1); + + while (1) { + int find_result; + char *first_slash; + char *remainder = NULL; + + if (!t.buffer) { + void *tree; + unsigned char root[20]; + unsigned long size; + tree = read_object_with_reference(current_tree_sha1, + tree_type, &size, + root); + if (!tree) + goto done; + + ALLOC_GROW(parents, parents_nr + 1, parents_alloc); + parents[parents_nr].tree = tree; + parents[parents_nr].size = size; + hashcpy(parents[parents_nr].sha1, root); + parents_nr++; + + if (namebuf.buf[0] == '\0') { + hashcpy(result, root); + retval = FOUND; + goto done; + } + + if (!size) + goto done; + + /* descend */ + init_tree_desc(&t, tree, size); + } + + /* Handle symlinks to e.g. a//b by removing leading slashes */ + while (namebuf.buf[0] == '/') { + strbuf_remove(&namebuf, 0, 1); + } + + /* Split namebuf into a first component and a remainder */ + if ((first_slash = strchr(namebuf.buf, '/'))) { + *first_slash = 0; + remainder = first_slash + 1; + } + + if (!strcmp(namebuf.buf, "..")) { + struct dir_state *parent; + /* + * We could end up with .. in the namebuf if it + * appears in a symlink. + */ + + if (parents_nr == 1) { + if (remainder) + *first_slash = '/'; + strbuf_add(result_path, namebuf.buf, + namebuf.len); + *mode = 0; + retval = FOUND; + goto done; + } + parent = &parents[parents_nr - 1]; + free(parent->tree); + parents_nr--; + parent = &parents[parents_nr - 1]; + init_tree_desc(&t, parent->tree, parent->size); + strbuf_remove(&namebuf, 0, remainder ? 3 : 2); + continue; + } + + /* We could end up here via a symlink to dir/.. */ + if (namebuf.buf[0] == '\0') { + hashcpy(result, parents[parents_nr - 1].sha1); + retval = FOUND; + goto done; + } + + /* Look up the first (or only) path component in the tree. */ + find_result = find_tree_entry(&t, namebuf.buf, + current_tree_sha1, mode); + if (find_result) { + goto done; + } + + if (S_ISDIR(*mode)) { + if (!remainder) { + hashcpy(result, current_tree_sha1); + retval = FOUND; + goto done; + } + /* Descend the tree */ + t.buffer = NULL; + strbuf_remove(&namebuf, 0, + 1 + first_slash - namebuf.buf); + } else if (S_ISREG(*mode)) { + if (!remainder) { + hashcpy(result, current_tree_sha1); + retval = FOUND; + } else { + retval = NOT_DIR; + } + goto done; + } else if (S_ISLNK(*mode)) { + /* Follow a symlink */ + unsigned long link_len; + size_t len; + char *contents, *contents_start; + struct dir_state *parent; + enum object_type type; + + if (follows_remaining-- == 0) { + /* Too many symlinks followed */ + retval = SYMLINK_LOOP; + goto done; + } + + /* + * At this point, we have followed at a least + * one symlink, so on error we need to report this. + */ + retval = DANGLING_SYMLINK; + + contents = read_sha1_file(current_tree_sha1, &type, + &link_len); + + if (!contents) + goto done; + + if (contents[0] == '/') { + strbuf_addstr(result_path, contents); + free(contents); + *mode = 0; + retval = FOUND; + goto done; + } + + if (remainder) + len = first_slash - namebuf.buf; + else + len = namebuf.len; + + contents_start = contents; + + parent = &parents[parents_nr - 1]; + init_tree_desc(&t, parent->tree, parent->size); + strbuf_splice(&namebuf, 0, len, + contents_start, link_len); + if (remainder) + namebuf.buf[link_len] = '/'; + free(contents); + } + } +done: + for (i = 0; i < parents_nr; i++) + free(parents[i].tree); + free(parents); + + strbuf_release(&namebuf); + return retval; +} + static int match_entry(const struct pathspec_item *item, const struct name_entry *entry, int pathlen, const char *match, int matchlen, @@ -728,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry, */ if (ps->recursive && S_ISDIR(entry->mode)) return entry_interesting; + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match + baselen, + entry->path, + item->nowildcard_len - baselen)) + return entry_interesting; } continue; @@ -764,6 +1053,21 @@ match_wildcards: strbuf_setlen(base, base_offset + baselen); return entry_interesting; } + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match, base->buf + base_offset, + item->nowildcard_len)) { + strbuf_setlen(base, base_offset + baselen); + return entry_interesting; + } + strbuf_setlen(base, base_offset + baselen); /* |