X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=apply.c;h=7c8146a7f317d9d1743e6a780eec823be72905a6;hb=3532998f407c5b4563ef15ffc52fe54e1f194993;hp=849a8b4485e65c7eac315daca60b781965a1d22e;hpb=806b8198cdbd3c838d7cbd94f4f256aa71389891;p=git.git diff --git a/apply.c b/apply.c index 849a8b44..7c8146a7 100644 --- a/apply.c +++ b/apply.c @@ -9,6 +9,8 @@ #include #include "cache.h" #include "quote.h" +#include "blob.h" +#include "delta.h" // --check turns on checking that the working tree matches the // files that are being modified, but doesn't apply the patch @@ -18,6 +20,7 @@ // static const char *prefix; static int prefix_length = -1; +static int newfd = -1; static int p_value = 1; static int allow_binary_replacement = 0; @@ -31,8 +34,9 @@ static int apply = 1; static int no_add = 0; static int show_index_info = 0; static int line_termination = '\n'; +static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=] ..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; static enum whitespace_eol { nowarn_whitespace, @@ -99,6 +103,7 @@ static int max_change, max_len; static int linenr = 1; struct fragment { + unsigned long leading, trailing; unsigned long oldpos, oldlines; unsigned long newpos, newlines; const char *patch; @@ -110,6 +115,9 @@ struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; int is_rename, is_copy, is_new, is_delete, is_binary; +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + unsigned long deflate_origlen; int lines_added, lines_deleted; int score; struct fragment *fragments; @@ -651,7 +659,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch len = linelen(line, size); if (!len || line[len-1] != '\n') break; - for (i = 0; i < sizeof(optable) / sizeof(optable[0]); i++) { + for (i = 0; i < ARRAY_SIZE(optable); i++) { const struct opentry *p = optable + i; int oplen = strlen(p->str); if (len < oplen || memcmp(p->str, line, oplen)) @@ -693,7 +701,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect line += digits; len -= digits; - *p2 = *p1; + *p2 = 1; if (*line == ',') { digits = parse_num(line+1, p2); if (!digits) @@ -816,12 +824,15 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s int added, deleted; int len = linelen(line, size), offset; unsigned long oldlines, newlines; + unsigned long leading, trailing; offset = parse_fragment_header(line, len, fragment); if (offset < 0) return -1; oldlines = fragment->oldlines; newlines = fragment->newlines; + leading = 0; + trailing = 0; if (patch->is_new < 0) { patch->is_new = !oldlines; @@ -834,7 +845,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s patch->new_name = NULL; } - if (patch->is_new != !oldlines) + if (patch->is_new && oldlines) return error("new file depends on old contents"); if (patch->is_delete != !newlines) { if (newlines) @@ -859,10 +870,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s case ' ': oldlines--; newlines--; + if (!deleted && !added) + leading++; + trailing++; break; case '-': deleted++; oldlines--; + trailing = 0; break; case '+': /* @@ -886,6 +901,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s } added++; newlines--; + trailing = 0; break; /* We allow "\ No newline at end of file". Depending @@ -901,6 +917,11 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s break; } } + if (oldlines || newlines) + return -1; + fragment->leading = leading; + fragment->trailing = trailing; + /* If a fragment ends with an incomplete line, we failed to include * it in the above loop because we hit oldlines == newlines == 0 * before seeing it. @@ -922,8 +943,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc struct fragment *fragment; int len; - fragment = xmalloc(sizeof(*fragment)); - memset(fragment, 0, sizeof(*fragment)); + fragment = xcalloc(1, sizeof(*fragment)); len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); @@ -951,6 +971,88 @@ static inline int metadata_changes(struct patch *patch) patch->old_mode != patch->new_mode); } +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "deflated + * literal" or "deflated delta") and the length of data before + * deflating; a sequence of 'length-byte' followed by base-85 + * encoded data follows. + * + * Each 5-byte sequence of base-85 encodes up to 4 bytes, + * and we would limit the patch line to 66 characters, + * so one line can fit up to 13 groups that would decode + * to 52 bytes max. The length byte 'A'-'Z' corresponds + * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. + * The end of binary is signalled with an empty line. + */ + int llen, used; + struct fragment *fragment; + char *data = NULL; + + patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); + + /* Grab the type of patch */ + llen = linelen(buffer, size); + used = llen; + linenr++; + + if (!strncmp(buffer, "delta ", 6)) { + patch->is_binary = BINARY_DELTA_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + } + else if (!strncmp(buffer, "literal ", 8)) { + patch->is_binary = BINARY_LITERAL_DEFLATED; + patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + } + else + return error("unrecognized binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + buffer += llen; + while (1) { + int byte_length, max_byte_length, newsize; + llen = linelen(buffer, size); + used += llen; + linenr++; + if (llen == 1) + break; + /* Minimum line is "A00000\n" which is 7-byte long, + * and the line length must be multiple of 5 plus 2. + */ + if ((llen < 7) || (llen-2) % 5) + goto corrupt; + max_byte_length = (llen - 2) / 5 * 4; + byte_length = *buffer; + if ('A' <= byte_length && byte_length <= 'Z') + byte_length = byte_length - 'A' + 1; + else if ('a' <= byte_length && byte_length <= 'z') + byte_length = byte_length - 'a' + 27; + else + goto corrupt; + /* if the input length was not multiple of 4, we would + * have filler at the end but the filler should never + * exceed 3 bytes + */ + if (max_byte_length < byte_length || + byte_length <= max_byte_length - 4) + goto corrupt; + newsize = fragment->size + byte_length; + data = xrealloc(data, newsize); + if (decode_85(data + fragment->size, + buffer + 1, + byte_length)) + goto corrupt; + fragment->size = newsize; + buffer += llen; + size -= llen; + } + fragment->patch = data; + return used; + corrupt: + return error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); +} + static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) { int hdrsize, patchsize; @@ -967,19 +1069,34 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) "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); - if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) + if (llen == sizeof(git_binary) - 1 && + !memcmp(git_binary, buffer + hd, llen)) { + int used; + linenr++; + used = parse_binary(buffer + hd + llen, + size - hd - llen, patch); + if (used) + patchsize = used + llen; + else + patchsize = 0; + } + else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { for (i = 0; binhdr[i]; i++) { int len = strlen(binhdr[i]); if (len < size - hd && !memcmp(binhdr[i], buffer + hd, len)) { + linenr++; patch->is_binary = 1; + patchsize = llen; break; } } + } /* Empty patch cannot be applied if: * - it is a binary patch and we do not do binary_replace, or @@ -1085,7 +1202,7 @@ static int read_old_data(struct stat *st, const char *path, void *buf, unsigned } } -static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line) +static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines) { int i; unsigned long start, backwards, forwards; @@ -1146,6 +1263,7 @@ static int find_offset(const char *buf, unsigned long size, const char *fragment n = (i >> 1)+1; if (i & 1) n = -n; + *lines = n; return try; } @@ -1155,6 +1273,33 @@ static int find_offset(const char *buf, unsigned long size, const char *fragment return -1; } +static void remove_first_line(const char **rbuf, int *rsize) +{ + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = 0; + while (offset <= size) { + if (buf[offset++] == '\n') + break; + } + *rsize = size - offset; + *rbuf = buf + offset; +} + +static void remove_last_line(const char **rbuf, int *rsize) +{ + const char *buf = *rbuf; + int size = *rsize; + unsigned long offset; + offset = size - 1; + while (offset > 0) { + if (buf[--offset] == '\n') + break; + } + *rsize = offset + 1; +} + struct buffer_desc { char *buffer; unsigned long size; @@ -1190,7 +1335,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) int offset, size = frag->size; char *old = xmalloc(size); char *new = xmalloc(size); + const char *oldlines, *newlines; int oldsize = 0, newsize = 0; + unsigned long leading, trailing; + int pos, lines; while (size > 0) { int len = linelen(patch, size); @@ -1239,23 +1387,59 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) newsize--; } #endif - - offset = find_offset(buf, desc->size, old, oldsize, frag->newpos); - if (offset >= 0) { - int diff = newsize - oldsize; - unsigned long size = desc->size + diff; - unsigned long alloc = desc->alloc; - - if (size > alloc) { - alloc = size + 8192; - desc->alloc = alloc; - buf = xrealloc(buf, alloc); - desc->buffer = buf; + + oldlines = old; + newlines = new; + leading = frag->leading; + trailing = frag->trailing; + lines = 0; + pos = frag->newpos; + for (;;) { + offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + if (offset >= 0) { + int diff = newsize - oldsize; + unsigned long size = desc->size + diff; + unsigned long alloc = desc->alloc; + + /* Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading) || (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + leading, trailing, pos + lines); + + if (size > alloc) { + alloc = size + 8192; + desc->alloc = alloc; + buf = xrealloc(buf, alloc); + desc->buffer = buf; + } + desc->size = size; + memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memcpy(buf + offset, newlines, newsize); + offset = 0; + + break; + } + + /* Am I at my context limits? */ + if ((leading <= p_context) && (trailing <= p_context)) + break; + /* Reduce the number of context lines + * Reduce both leading and trailing if they are equal + * otherwise just reduce the larger context. + */ + if (leading >= trailing) { + remove_first_line(&oldlines, &oldsize); + remove_first_line(&newlines, &newsize); + pos--; + leading--; + } + if (trailing > leading) { + remove_last_line(&oldlines, &oldsize); + remove_last_line(&newlines, &newsize); + trailing--; } - desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); - memcpy(buf + offset, new, newsize); - offset = 0; } free(old); @@ -1263,76 +1447,150 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) return offset; } -static int apply_fragments(struct buffer_desc *desc, struct patch *patch) +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) +{ + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) +{ + unsigned long dst_size; + struct fragment *fragment = patch->fragments; + void *data; + void *result; + + data = inflate_it(fragment->patch, fragment->size, + patch->deflate_origlen); + if (!data) + return error("corrupt patch data"); + switch (patch->is_binary) { + case BINARY_DELTA_DEFLATED: + result = patch_delta(desc->buffer, desc->size, + data, + patch->deflate_origlen, + &dst_size); + free(desc->buffer); + desc->buffer = result; + free(data); + break; + case BINARY_LITERAL_DEFLATED: + free(desc->buffer); + desc->buffer = data; + dst_size = patch->deflate_origlen; + break; + } + if (!desc->buffer) + return -1; + desc->size = desc->alloc = dst_size; + return 0; +} + +static int apply_binary(struct buffer_desc *desc, struct patch *patch) { - struct fragment *frag = patch->fragments; const char *name = patch->old_name ? patch->old_name : patch->new_name; + unsigned char sha1[20]; + unsigned char hdr[50]; + int hdrlen; - if (patch->is_binary) { - unsigned char sha1[20]; + if (!allow_binary_replacement) + return error("cannot apply binary patch to '%s' " + "without --allow-binary-replacement", + name); - if (!allow_binary_replacement) - return error("cannot apply binary patch to '%s' " - "without --allow-binary-replacement", - name); + /* For safety, we require patch index line to contain + * full 40-byte textual SHA1 for old and new, at least for now. + */ + if (strlen(patch->old_sha1_prefix) != 40 || + strlen(patch->new_sha1_prefix) != 40 || + get_sha1_hex(patch->old_sha1_prefix, sha1) || + get_sha1_hex(patch->new_sha1_prefix, sha1)) + return error("cannot apply binary patch to '%s' " + "without full index line", name); - /* For safety, we require patch index line to contain - * full 40-byte textual SHA1 for old and new, at least for now. + if (patch->old_name) { + /* See if the old one matches what the patch + * applies to. */ - if (strlen(patch->old_sha1_prefix) != 40 || - strlen(patch->new_sha1_prefix) != 40 || - get_sha1_hex(patch->old_sha1_prefix, sha1) || - get_sha1_hex(patch->new_sha1_prefix, sha1)) - return error("cannot apply binary patch to '%s' " - "without full index line", name); - - if (patch->old_name) { - unsigned char hdr[50]; - int hdrlen; - - /* See if the old one matches what the patch - * applies to. - */ - write_sha1_file_prepare(desc->buffer, desc->size, - "blob", sha1, hdr, &hdrlen); - if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) - return error("the patch applies to '%s' (%s), " - "which does not match the " - "current contents.", - name, sha1_to_hex(sha1)); - } - else { - /* Otherwise, the old one must be empty. */ - if (desc->size) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } + write_sha1_file_prepare(desc->buffer, desc->size, + blob_type, sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) + return error("the patch applies to '%s' (%s), " + "which does not match the " + "current contents.", + name, sha1_to_hex(sha1)); + } + else { + /* Otherwise, the old one must be empty. */ + if (desc->size) + return error("the patch applies to an empty " + "'%s' but it is not empty", name); + } + + get_sha1_hex(patch->new_sha1_prefix, sha1); + if (!memcmp(sha1, null_sha1, 20)) { + free(desc->buffer); + desc->alloc = desc->size = 0; + desc->buffer = NULL; + return 0; /* deletion patch */ + } + + if (has_sha1_file(sha1)) { + /* We already have the postimage */ + char type[10]; + unsigned long size; - /* For now, we do not record post-image data in the patch, - * and require the object already present in the recipient's - * object database. + free(desc->buffer); + desc->buffer = read_sha1_file(sha1, type, &size); + if (!desc->buffer) + return error("the necessary postimage %s for " + "'%s' cannot be read", + patch->new_sha1_prefix, name); + desc->alloc = desc->size = size; + } + else { + /* We have verified desc matches the preimage; + * apply the patch data to it, which is stored + * in the patch->fragments->{patch,size}. */ - if (desc->buffer) { - free(desc->buffer); - desc->alloc = desc->size = 0; - } - get_sha1_hex(patch->new_sha1_prefix, sha1); - - if (memcmp(sha1, null_sha1, 20)) { - char type[10]; - unsigned long size; - - desc->buffer = read_sha1_file(sha1, type, &size); - if (!desc->buffer) - return error("the necessary postimage %s for " - "'%s' does not exist", - patch->new_sha1_prefix, name); - desc->alloc = desc->size = size; - } + if (apply_binary_fragment(desc, patch)) + return error("binary patch does not apply to '%s'", + name); - return 0; + /* verify that the result matches */ + write_sha1_file_prepare(desc->buffer, desc->size, blob_type, + sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) + return error("binary patch to '%s' creates incorrect result", name); } + return 0; +} + +static int apply_fragments(struct buffer_desc *desc, struct patch *patch) +{ + struct fragment *frag = patch->fragments; + const char *name = patch->old_name ? patch->old_name : patch->new_name; + + if (patch->is_binary) + return apply_binary(desc, patch); + while (frag) { if (apply_one_fragment(desc, frag) < 0) return error("patch failed: %s:%ld", @@ -1649,15 +1907,14 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned if (!write_index) return; - ce = xmalloc(ce_size); - memset(ce, 0, ce_size); + ce = xcalloc(1, ce_size); memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = htons(namelen); if (lstat(path, &st) < 0) die("unable to stat newly created file %s", path); fill_stat_cache_info(ce, &st); - if (write_sha1_file(buf, size, "blob", ce->sha1) < 0) + if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) die("unable to create backing store for newly created file %s", path); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) die("unable to add cache entry for %s", path); @@ -1792,7 +2049,6 @@ static int use_patch(struct patch *p) static int apply_patch(int fd, const char *filename) { - int newfd; unsigned long offset, size; char *buffer = read_patch_file(fd, &size); struct patch *list = NULL, **listp = &list; @@ -1806,8 +2062,7 @@ static int apply_patch(int fd, const char *filename) struct patch *patch; int nr; - patch = xmalloc(sizeof(*patch)); - memset(patch, 0, sizeof(*patch)); + patch = xcalloc(1, sizeof(*patch)); nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; @@ -1824,12 +2079,11 @@ static int apply_patch(int fd, const char *filename) size -= nr; } - newfd = -1; if (whitespace_error && (new_whitespace == error_on_whitespace)) apply = 0; write_index = check_index && apply; - if (write_index) + if (write_index && newfd < 0) newfd = hold_index_file_for_update(&cache_file, get_index_file()); if (check_index) { if (read_cache() < 0) @@ -1842,12 +2096,6 @@ static int apply_patch(int fd, const char *filename) if (apply) write_out_results(list, skipped_patch); - if (write_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_index_file(&cache_file)) - die("Unable to write new cachefile"); - } - if (show_index_info) show_index_list(list); @@ -1882,6 +2130,7 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + char *end; int fd; if (!strcmp(arg, "-")) { @@ -1909,7 +2158,8 @@ int main(int argc, char **argv) diffstat = 1; continue; } - if (!strcmp(arg, "--allow-binary-replacement")) { + if (!strcmp(arg, "--allow-binary-replacement") || + !strcmp(arg, "--binary")) { allow_binary_replacement = 1; continue; } @@ -1945,6 +2195,12 @@ int main(int argc, char **argv) line_termination = 0; continue; } + if (!strncmp(arg, "-C", 2)) { + p_context = strtoul(arg + 2, &end, 0); + if (*end != '\0') + die("unrecognized context count '%s'", arg + 2); + continue; + } if (!strncmp(arg, "--whitespace=", 13)) { whitespace_option = arg + 13; parse_whitespace_option(arg + 13); @@ -1998,5 +2254,12 @@ int main(int argc, char **argv) whitespace_error == 1 ? "" : "s", whitespace_error == 1 ? "s" : ""); } + + if (write_index) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new cachefile"); + } + return 0; }