From: Junio C Hamano Date: Tue, 3 Mar 2015 22:37:01 +0000 (-0800) Subject: Merge branch 'jc/apply-beyond-symlink' X-Git-Tag: v2.4.0-rc0~79 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/71f19cce36103dce545e647e707be2e9a474139a?hp=-c Merge branch 'jc/apply-beyond-symlink' "git apply" was not very careful about reading from, removing, updating and creating paths outside the working tree (under --index/--cached) or the current directory (when used as a replacement for GNU patch). * jc/apply-beyond-symlink: apply: do not touch a file beyond a symbolic link apply: do not read from beyond a symbolic link apply: do not read from the filesystem under --index apply: reject input that touches outside the working area --- 71f19cce36103dce545e647e707be2e9a474139a diff --combined builtin/apply.c index 955cc79499,c2c6fda08c..525db4a2a2 --- a/builtin/apply.c +++ b/builtin/apply.c @@@ -7,7 -7,6 +7,7 @@@ * */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "quote.h" #include "blob.h" @@@ -51,11 -50,12 +51,12 @@@ static int apply_verbosely static int allow_overlap; static int no_add; static int threeway; + static int unsafe_paths; static const char *fake_ancestor; static int line_termination = '\n'; static unsigned int p_context = UINT_MAX; static const char * const apply_usage[] = { - N_("git apply [options] [...]"), + N_("git apply [] [...]"), NULL }; @@@ -301,13 -301,11 +302,13 @@@ static int fuzzy_matchlines(const char while ((*last2 == '\r') || (*last2 == '\n')) last2--; - /* skip leading whitespace */ - while (isspace(*s1) && (s1 <= last1)) - s1++; - while (isspace(*s2) && (s2 <= last2)) - s2++; + /* skip leading whitespaces, if both begin with whitespace */ + if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) { + while (isspace(*s1) && (s1 <= last1)) + s1++; + while (isspace(*s2) && (s2 <= last2)) + s2++; + } /* early return if both lines are empty */ if ((s1 > last1) && (s2 > last2)) return 1; @@@ -436,7 -434,7 +437,7 @@@ static unsigned long linelen(const cha static int is_dev_null(const char *str) { - return !memcmp("/dev/null", str, 9) && isspace(str[9]); + return skip_prefix(str, "/dev/null", &str) && isspace(*str); } #define TERM_SPACE 1 @@@ -657,6 -655,11 +658,6 @@@ static size_t diff_timestamp_len(const return line + len - end; } -static char *null_strdup(const char *s) -{ - return s ? xstrdup(s) : NULL; -} - static char *find_name_common(const char *line, const char *def, int p_value, const char *end, int terminate) { @@@ -679,10 -682,10 +680,10 @@@ start = line; } if (!start) - return squash_slash(null_strdup(def)); + return squash_slash(xstrdup_or_null(def)); len = line - start; if (!len) - return squash_slash(null_strdup(def)); + return squash_slash(xstrdup_or_null(def)); /* * Generally we prefer the shorter name, especially @@@ -904,7 -907,7 +905,7 @@@ static void parse_traditional_patch(con patch->old_name = name; } else { patch->old_name = name; - patch->new_name = null_strdup(name); + patch->new_name = xstrdup_or_null(name); } } if (!name) @@@ -993,7 -996,7 +994,7 @@@ static int gitdiff_delete(const char *l { patch->is_delete = 1; free(patch->old_name); - patch->old_name = null_strdup(patch->def_name); + patch->old_name = xstrdup_or_null(patch->def_name); return gitdiff_oldmode(line, patch); } @@@ -1001,7 -1004,7 +1002,7 @@@ static int gitdiff_newfile(const char * { patch->is_new = 1; free(patch->new_name); - patch->new_name = null_strdup(patch->def_name); + patch->new_name = xstrdup_or_null(patch->def_name); return gitdiff_newmode(line, patch); } @@@ -1071,7 -1074,7 +1072,7 @@@ static int gitdiff_index(const char *li line = ptr + 2; ptr = strchr(line, ' '); - eol = strchr(line, '\n'); + eol = strchrnul(line, '\n'); if (!ptr || eol < ptr) ptr = eol; @@@ -1277,7 -1280,9 +1278,7 @@@ static int parse_git_header(const char */ patch->def_name = git_header_name(line, len); if (patch->def_name && root) { - char *s = xmalloc(root_len + strlen(patch->def_name) + 1); - strcpy(s, root); - strcpy(s + root_len, patch->def_name); + char *s = xstrfmt("%s%s", root, patch->def_name); free(patch->def_name); patch->def_name = s; } @@@ -1405,10 -1410,10 +1406,10 @@@ static void recount_diff(const char *li case '\\': continue; case '@': - ret = size < 3 || prefixcmp(line, "@@ "); + ret = size < 3 || !starts_with(line, "@@ "); break; case 'd': - ret = size < 5 || prefixcmp(line, "diff "); + ret = size < 5 || !starts_with(line, "diff "); break; default: ret = -1; @@@ -1794,11 -1799,11 +1795,11 @@@ static struct fragment *parse_binary_hu *status_p = 0; - if (!prefixcmp(buffer, "delta ")) { + if (starts_with(buffer, "delta ")) { patch_method = BINARY_DELTA_DEFLATED; origlen = strtoul(buffer + 6, NULL, 10); } - else if (!prefixcmp(buffer, "literal ")) { + else if (starts_with(buffer, "literal ")) { patch_method = BINARY_LITERAL_DEFLATED; origlen = strtoul(buffer + 8, NULL, 10); } @@@ -1916,66 -1921,6 +1917,66 @@@ static int parse_binary(char *buffer, u return used; } +static void prefix_one(char **name) +{ + char *old_name = *name; + if (!old_name) + return; + *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); + free(old_name); +} + +static void prefix_patch(struct patch *p) +{ + if (!prefix || p->is_toplevel_relative) + return; + prefix_one(&p->new_name); + prefix_one(&p->old_name); +} + +/* + * include/exclude + */ + +static struct string_list limit_by_name; +static int has_include; +static void add_name_limit(const char *name, int exclude) +{ + struct string_list_item *it; + + it = string_list_append(&limit_by_name, name); + it->util = exclude ? NULL : (void *) 1; +} + +static int use_patch(struct patch *p) +{ + const char *pathname = p->new_name ? p->new_name : p->old_name; + int i; + + /* Paths outside are not touched regardless of "--include" */ + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } + + /* See if it matches any of exclude/include rule */ + for (i = 0; i < limit_by_name.nr; i++) { + struct string_list_item *it = &limit_by_name.items[i]; + if (!wildmatch(it->string, pathname, 0, NULL)) + return (it->util != NULL); + } + + /* + * If we had any include, a path that does not match any rule is + * not used. Otherwise, we saw bunch of exclude rules (or none) + * and such a path is used. + */ + return !has_include; +} + + /* * Read the patch text in "buffer" that extends for "size" bytes; stop * reading after seeing a single patch (i.e. changes to a single file). @@@ -1991,20 -1936,21 +1992,20 @@@ static int parse_chunk(char *buffer, un if (offset < 0) return offset; - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); + prefix_patch(patch); + + if (!use_patch(patch)) + patch->ws_rule = 0; + else + patch->ws_rule = whitespace_rule(patch->new_name + ? patch->new_name + : patch->old_name); patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); if (!patchsize) { - static const char *binhdr[] = { - "Binary files ", - "Files ", - NULL, - }; static const char git_binary[] = "GIT binary patch\n"; - int i; int hd = hdrsize + offset; unsigned long llen = linelen(buffer + hd, size - hd); @@@ -2020,12 -1966,6 +2021,12 @@@ patchsize = 0; } else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { + static const char *binhdr[] = { + "Binary files ", + "Files ", + NULL, + }; + int i; for (i = 0; binhdr[i]; i++) { int len = strlen(binhdr[i]); if (len < size - hd && @@@ -2230,12 -2170,6 +2231,12 @@@ static void update_pre_post_images(stru ctx++; } + if (postlen + ? postlen < new - postimage->buf + : postimage->len < new - postimage->buf) + die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d", + (int)postlen, (int) postimage->len, (int)(new - postimage->buf)); + /* Fix the length of the whole thing */ postimage->len = new - postimage->buf; postimage->nr -= reduced; @@@ -2391,27 -2325,10 +2392,27 @@@ static int match_fragment(struct image /* * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We haven't been asked to + * it might with whitespace fuzz. We weren't asked to * ignore whitespace, we were asked to correct whitespace * errors, so let's try matching after whitespace correction. * + * While checking the preimage against the target, whitespace + * errors in both fixed, we count how large the corresponding + * postimage needs to be. The postimage prepared by + * apply_one_fragment() has whitespace errors fixed on added + * lines already, but the common lines were propagated as-is, + * which may become longer when their whitespace errors are + * fixed. + */ + + /* First count added lines in postimage */ + postlen = 0; + for (i = 0; i < postimage->nr; i++) { + if (!(postimage->line[i].flag & LINE_COMMON)) + postlen += postimage->line[i].len; + } + + /* * The preimage may extend beyond the end of the file, * but in this loop we will only handle the part of the * preimage that falls within the file. @@@ -2419,6 -2336,7 +2420,6 @@@ strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; - postlen = 0; for (i = 0; i < preimage_limit; i++) { size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; @@@ -2446,10 -2364,7 +2447,10 @@@ match = (tgtfix.len == fixed.len - fixstart && !memcmp(tgtfix.buf, fixed.buf + fixstart, fixed.len - fixstart)); - postlen += tgtfix.len; + + /* Add the length if this is common with the postimage */ + if (preimage->line[i].flag & LINE_COMMON) + postlen += tgtfix.len; strbuf_release(&tgtfix); if (!match) @@@ -2647,7 -2562,7 +2648,7 @@@ static void update_image(struct image * * NOTE: this knows that we never call remove_first_line() * on anything other than pre/post image. */ - img->line = xrealloc(img->line, nr * sizeof(*img->line)); + REALLOC_ARRAY(img->line, nr); img->line_allocated = img->line; } if (preimage_limit != postimage->nr) @@@ -2953,7 -2868,9 +2954,7 @@@ static int apply_binary_fragment(struc case BINARY_LITERAL_DEFLATED: clear_image(img); img->len = fragment->size; - img->buf = xmalloc(img->len+1); - memcpy(img->buf, fragment->patch, img->len); - img->buf[img->len] = '\0'; + img->buf = xmemdupz(fragment->patch, img->len); return 0; } return -1; @@@ -3168,15 -3085,13 +3169,15 @@@ static void prepare_fn_table(struct pat } } -static int checkout_target(struct cache_entry *ce, struct stat *st) +static int checkout_target(struct index_state *istate, + struct cache_entry *ce, struct stat *st) { struct checkout costate; memset(&costate, 0, sizeof(costate)); costate.base_dir = ""; costate.refresh_cache = 1; + costate.istate = istate; if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) return error(_("cannot checkout %s"), ce->name); return 0; @@@ -3221,7 -3136,7 +3222,7 @@@ static int load_patch_target(struct str const char *name, unsigned expected_mode) { - if (cached) { + if (cached || check_index) { if (read_file_or_gitlink(ce, buf)) return error(_("read of %s failed"), name); } else if (name) { @@@ -3230,6 -3145,8 +3231,8 @@@ return read_file_or_gitlink(ce, buf); else return SUBMODULE_PATCH_WITHOUT_INDEX; + } else if (has_symlink_leading_path(name, strlen(name))) { + return error(_("reading from '%s' beyond a symbolic link"), name); } else { if (read_old_data(st, name, buf)) return error(_("read of %s failed"), name); @@@ -3343,7 -3260,7 +3346,7 @@@ static int load_current(struct image *i if (lstat(name, &st)) { if (errno != ENOENT) return error(_("%s: %s"), name, strerror(errno)); - if (checkout_target(ce, &st)) + if (checkout_target(&the_index, ce, &st)) return -1; } if (verify_index_match(ce, &st)) @@@ -3497,7 -3414,7 +3500,7 @@@ static int check_preimage(struct patch } *ce = active_cache[pos]; if (stat_ret < 0) { - if (checkout_target(*ce, st)) + if (checkout_target(&the_index, *ce, st)) return -1; } if (!cached && verify_index_match(*ce, st)) @@@ -3569,6 -3486,121 +3572,121 @@@ static int check_to_create(const char * return 0; } + /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ + static struct string_list symlink_changes; + #define SYMLINK_GOES_AWAY 01 + #define SYMLINK_IN_RESULT 02 + + static uintptr_t register_symlink_changes(const char *path, uintptr_t what) + { + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) { + ent = string_list_insert(&symlink_changes, path); + ent->util = (void *)0; + } + ent->util = (void *)(what | ((uintptr_t)ent->util)); + return (uintptr_t)ent->util; + } + + static uintptr_t check_symlink_changes(const char *path) + { + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) + return 0; + return (uintptr_t)ent->util; + } + + static void prepare_symlink_changes(struct patch *patch) + { + for ( ; patch; patch = patch->next) { + if ((patch->old_name && S_ISLNK(patch->old_mode)) && + (patch->is_rename || patch->is_delete)) + /* the symlink at patch->old_name is removed */ + register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); + + if (patch->new_name && S_ISLNK(patch->new_mode)) + /* the symlink at patch->new_name is created or remains */ + register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); + } + } + + static int path_is_beyond_symlink_1(struct strbuf *name) + { + do { + unsigned int change; + + while (--name->len && name->buf[name->len] != '/') + ; /* scan backwards */ + if (!name->len) + break; + name->buf[name->len] = '\0'; + change = check_symlink_changes(name->buf); + if (change & SYMLINK_IN_RESULT) + return 1; + if (change & SYMLINK_GOES_AWAY) + /* + * This cannot be "return 0", because we may + * see a new one created at a higher level. + */ + continue; + + /* otherwise, check the preimage */ + if (check_index) { + struct cache_entry *ce; + + ce = cache_file_exists(name->buf, name->len, ignore_case); + if (ce && S_ISLNK(ce->ce_mode)) + return 1; + } else { + struct stat st; + if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) + return 1; + } + } while (1); + return 0; + } + + static int path_is_beyond_symlink(const char *name_) + { + int ret; + struct strbuf name = STRBUF_INIT; + + assert(*name_ != '\0'); + strbuf_addstr(&name, name_); + ret = path_is_beyond_symlink_1(&name); + strbuf_release(&name); + + return ret; + } + + static void die_on_unsafe_path(struct patch *patch) + { + const char *old_name = NULL; + const char *new_name = NULL; + if (patch->is_delete) + old_name = patch->old_name; + else if (!patch->is_new && !patch->is_copy) + old_name = patch->old_name; + if (!patch->is_delete) + new_name = patch->new_name; + + if (old_name && !verify_path(old_name)) + die(_("invalid path '%s'"), old_name); + if (new_name && !verify_path(new_name)) + die(_("invalid path '%s'"), new_name); + } + /* * Check and apply the patch in-core; leave the result in patch->result * for the caller to write it out to the final destination. @@@ -3656,6 -3688,22 +3774,22 @@@ static int check_patch(struct patch *pa } } + if (!unsafe_paths) + die_on_unsafe_path(patch); + + /* + * An attempt to read from or delete a path that is beyond a + * symbolic link will be prevented by load_patch_target() that + * is called at the beginning of apply_data() so we do not + * have to worry about a patch marked with "is_delete" bit + * here. We however need to make sure that the patch result + * is not deposited to a path that is beyond a symbolic link + * here. + */ + if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) + return error(_("affected file '%s' is beyond a symbolic link"), + patch->new_name); + if (apply_data(patch, &st, ce) < 0) return error(_("%s: patch does not apply"), name); patch->rejected = 0; @@@ -3666,6 -3714,7 +3800,7 @@@ static int check_patch_list(struct patc { int err = 0; + prepare_symlink_changes(patch); prepare_fn_table(patch); while (patch) { if (apply_verbosely) @@@ -3713,12 -3762,12 +3848,12 @@@ static int preimage_sha1_in_gitlink_pat hunk->oldpos == 1 && hunk->oldlines == 1 && /* does preimage begin with the heading? */ (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && - !prefixcmp(++preimage, heading) && + starts_with(++preimage, heading) && /* does it record full SHA-1? */ !get_sha1_hex(preimage + sizeof(heading) - 1, sha1) && preimage[sizeof(heading) + 40 - 1] == '\n' && /* does the abbreviated name on the index line agree with it? */ - !prefixcmp(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) + starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) return 0; /* it all looks fine */ /* we may have full object name on the index line */ @@@ -3730,7 -3779,7 +3865,7 @@@ static void build_fake_ancestor(struct { struct patch *patch; struct index_state result = { NULL }; - int fd; + static struct lock_file lock; /* Once we start supporting the reverse patch, it may be * worth showing the new sha1 prefix, but until then... @@@ -3748,7 -3797,7 +3883,7 @@@ if (!preimage_sha1_in_gitlink_patch(patch, sha1)) ; /* ok, the textual part looks sane */ else - die("sha1 information is lacking or useless for submoule %s", + die("sha1 information is lacking or useless for submodule %s", name); } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { ; /* ok */ @@@ -3768,8 -3817,8 +3903,8 @@@ die ("Could not add %s to temporary index", name); } - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0 || write_index(&result, fd) || close(fd)) + hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + if (write_locked_index(&result, &lock, COMMIT_LOCK)) die ("Could not write temporary index to %s", filename); discard_index(&result); @@@ -3931,10 -3980,9 +4066,10 @@@ static void add_index_file(const char * ce->ce_flags = create_ce_flags(0); ce->ce_namelen = namelen; if (S_ISGITLINK(mode)) { - const char *s = buf; + const char *s; - if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) + if (!skip_prefix(buf, "Subproject commit ", &s) || + get_sha1_hex(s, ce->sha1)) die(_("corrupt patch for submodule %s"), path); } else { if (!cached) { @@@ -4148,7 -4196,7 +4283,7 @@@ static int write_out_one_reject(struct return error(_("cannot open %s: %s"), namebuf, strerror(errno)); /* Normal git tools never deal with .rej, so do not pretend - * this is a git patch by saying --git nor give extended + * this is a git patch by saying --git or giving extended * headers. While at it, maybe please "kompare" that wants * the trailing TAB and some garbage at the end of line ;-). */ @@@ -4200,7 -4248,7 +4335,7 @@@ static int write_out_results(struct pat if (cpath.nr) { struct string_list_item *item; - sort_string_list(&cpath); + string_list_sort(&cpath); for_each_string_list_item(item, &cpath) fprintf(stderr, "U %s\n", item->string); string_list_clear(&cpath, 0); @@@ -4213,6 -4261,64 +4348,6 @@@ static struct lock_file lock_file; -static struct string_list limit_by_name; -static int has_include; -static void add_name_limit(const char *name, int exclude) -{ - struct string_list_item *it; - - it = string_list_append(&limit_by_name, name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < limit_by_name.nr; i++) { - struct string_list_item *it = &limit_by_name.items[i]; - if (!fnmatch(it->string, pathname, 0)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !has_include; -} - - -static void prefix_one(char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); - free(old_name); -} - -static void prefix_patches(struct patch *p) -{ - if (!prefix || p->is_toplevel_relative) - return; - for ( ; p; p = p->next) { - prefix_one(&p->new_name); - prefix_one(&p->old_name); - } -} - #define INACCURATE_EOF (1<<0) #define RECOUNT (1<<1) @@@ -4238,6 -4344,8 +4373,6 @@@ static int apply_patch(int fd, const ch break; if (apply_in_reverse) reverse_patches(patch); - if (prefix) - prefix_patches(patch); if (use_patch(patch)) { patch_stats(patch); *listp = patch; @@@ -4295,11 -4403,13 +4430,11 @@@ return 0; } -static int git_apply_config(const char *var, const char *value, void *cb) +static void git_apply_config(void) { - if (!strcmp(var, "apply.whitespace")) - return git_config_string(&apply_default_whitespace, var, value); - else if (!strcmp(var, "apply.ignorewhitespace")) - return git_config_string(&apply_default_ignorewhitespace, var, value); - return git_default_config(var, value, cb); + git_config_get_string_const("apply.whitespace", &apply_default_whitespace); + git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); + git_config(git_default_config, NULL); } static int option_parse_exclude(const struct option *opt, @@@ -4404,6 -4514,8 +4539,8 @@@ int cmd_apply(int argc, const char **ar N_("make sure the patch is applicable to the current index")), OPT_BOOL(0, "cached", &cached, N_("apply a patch without touching the working tree")), + OPT_BOOL(0, "unsafe-paths", &unsafe_paths, + N_("accept a patch that touches outside the working area")), OPT_BOOL(0, "apply", &force_apply, N_("also apply the patch (use with --stat/--summary/--check)")), OPT_BOOL('3', "3way", &threeway, @@@ -4447,7 -4559,7 +4584,7 @@@ prefix = prefix_; prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config, NULL); + git_apply_config(); if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); if (apply_default_ignorewhitespace) @@@ -4476,6 -4588,9 +4613,9 @@@ die(_("--cached outside a repository")); check_index = 1; } + if (check_index) + unsafe_paths = 0; + for (i = 0; i < argc; i++) { const char *arg = argv[i]; int fd; @@@ -4526,7 -4641,8 +4666,7 @@@ } if (update_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --combined t/t4122-apply-symlink-inside.sh index 1814647663,1779c0a568..4acb3f336e --- a/t/t4122-apply-symlink-inside.sh +++ b/t/t4122-apply-symlink-inside.sh @@@ -3,10 -3,17 +3,10 @@@ test_description='apply to deeper directory without getting fooled with symlink' . ./test-lib.sh -lecho () { - for l_ - do - echo "$l_" - done -} - test_expect_success setup ' mkdir -p arch/i386/boot arch/x86_64 && - lecho 1 2 3 4 5 >arch/i386/boot/Makefile && + test_write_lines 1 2 3 4 5 >arch/i386/boot/Makefile && test_ln_s_add ../i386/boot arch/x86_64/boot && git add . && test_tick && @@@ -15,7 -22,7 +15,7 @@@ rm arch/x86_64/boot && mkdir arch/x86_64/boot && - lecho 2 3 4 5 6 >arch/x86_64/boot/Makefile && + test_write_lines 2 3 4 5 6 >arch/x86_64/boot/Makefile && git add . && test_tick && git commit -a -m second && @@@ -45,4 -52,110 +45,110 @@@ test_expect_success 'check result' ' + test_expect_success SYMLINKS 'do not read from beyond symbolic link' ' + git reset --hard && + mkdir -p arch/x86_64/dir && + >arch/x86_64/dir/file && + git add arch/x86_64/dir/file && + echo line >arch/x86_64/dir/file && + git diff >patch && + git reset --hard && + + mkdir arch/i386/dir && + >arch/i386/dir/file && + ln -s ../i386/dir arch/x86_64/dir && + + test_must_fail git apply patch && + test_must_fail git apply --cached patch && + test_must_fail git apply --index patch + + ' + + test_expect_success SYMLINKS 'do not follow symbolic link (setup)' ' + + rm -rf arch/i386/dir arch/x86_64/dir && + git reset --hard && + ln -s ../i386/dir arch/x86_64/dir && + git add arch/x86_64/dir && + git diff HEAD >add_symlink.patch && + git reset --hard && + + mkdir arch/x86_64/dir && + >arch/x86_64/dir/file && + git add arch/x86_64/dir/file && + git diff HEAD >add_file.patch && + git diff -R HEAD >del_file.patch && + git reset --hard && + rm -fr arch/x86_64/dir && + + cat add_symlink.patch add_file.patch >patch && + cat add_symlink.patch del_file.patch >tricky_del && + + mkdir arch/i386/dir + ' + + test_expect_success SYMLINKS 'do not follow symbolic link (same input)' ' + + # same input creates a confusing symbolic link + test_must_fail git apply patch 2>error-wt && + test_i18ngrep "beyond a symbolic link" error-wt && + test_path_is_missing arch/x86_64/dir && + test_path_is_missing arch/i386/dir/file && + + test_must_fail git apply --index patch 2>error-ix && + test_i18ngrep "beyond a symbolic link" error-ix && + test_path_is_missing arch/x86_64/dir && + test_path_is_missing arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached patch 2>error-ct && + test_i18ngrep "beyond a symbolic link" error-ct && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + >arch/i386/dir/file && + git add arch/i386/dir/file && + + test_must_fail git apply tricky_del && + test_path_is_file arch/i386/dir/file && + + test_must_fail git apply --index tricky_del && + test_path_is_file arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached tricky_del && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + git ls-files --error-unmatch arch/i386/dir + ' + + test_expect_success SYMLINKS 'do not follow symbolic link (existing)' ' + + # existing symbolic link + git reset --hard && + ln -s ../i386/dir arch/x86_64/dir && + git add arch/x86_64/dir && + + test_must_fail git apply add_file.patch 2>error-wt-add && + test_i18ngrep "beyond a symbolic link" error-wt-add && + test_path_is_missing arch/i386/dir/file && + + mkdir arch/i386/dir && + >arch/i386/dir/file && + test_must_fail git apply del_file.patch 2>error-wt-del && + test_i18ngrep "beyond a symbolic link" error-wt-del && + test_path_is_file arch/i386/dir/file && + rm arch/i386/dir/file && + + test_must_fail git apply --index add_file.patch 2>error-ix-add && + test_i18ngrep "beyond a symbolic link" error-ix-add && + test_path_is_missing arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached add_file.patch 2>error-ct-file && + test_i18ngrep "beyond a symbolic link" error-ct-file && + test_must_fail git ls-files --error-unmatch arch/i386/dir + ' + test_done