diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | apply.c | 317 |
2 files changed, 318 insertions, 1 deletions
@@ -31,7 +31,7 @@ PROG= git-update-cache git-diff-files git-init-db git-write-tree \ git-unpack-file git-export git-diff-cache git-convert-cache \ git-http-pull git-rpush git-rpull git-rev-list git-mktag \ git-diff-helper git-tar-tree git-local-pull git-write-blob \ - git-get-tar-commit-id git-mkdelta + git-get-tar-commit-id git-mkdelta git-apply all: $(PROG) diff --git a/apply.c b/apply.c new file mode 100644 index 000000000..85d7965da --- /dev/null +++ b/apply.c @@ -0,0 +1,317 @@ +/* + * apply.c + * + * Copyright (C) Linus Torvalds, 2005 + * + * This applies patches on top of some (arbitrary) version of the SCM. + * + * NOTE! It does all its work in the index file, and only cares about + * the files in the working directory if you tell it to "merge" the + * patch apply. + * + * Even when merging it always takes the source from the index, and + * uses the working tree as a "branch" for a 3-way merge. + */ +#include <ctype.h> + +#include "cache.h" + +// We default to the merge behaviour, since that's what most people would +// expect +static int merge_patch = 1; +static const char apply_usage[] = "git-apply <patch>"; + +#define CHUNKSIZE (8192) + +static void *read_patch_file(int fd, unsigned long *sizep) +{ + unsigned long size = 0, alloc = CHUNKSIZE; + void *buffer = xmalloc(alloc); + + for (;;) { + int nr = alloc - size; + if (nr < 1024) { + alloc += CHUNKSIZE; + buffer = xrealloc(buffer, alloc); + nr = alloc - size; + } + nr = read(fd, buffer + size, nr); + if (!nr) + break; + if (nr < 0) { + if (errno == EAGAIN) + continue; + die("git-apply: read returned %s", strerror(errno)); + } + size += nr; + } + *sizep = size; + return buffer; +} + +static unsigned long linelen(char *buffer, unsigned long size) +{ + unsigned long len = 0; + while (size--) { + len++; + if (*buffer++ == '\n') + break; + } + return len; +} + +static int match_word(const char *line, const char *match) +{ + for (;;) { + char c = *match++; + if (!c) + break; + if (*line++ != c) + return 0; + } + return *line == ' '; +} + +/* Verify that we recognize the lines following a git header */ +static int parse_git_header(char *line, unsigned int size) +{ + unsigned long offset, len; + + for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len) { + len = linelen(line, size); + if (!len) + break; + if (line[len-1] != '\n') + return -1; + if (len < 4) + break; + if (!memcmp(line, "@@ -", 4)) + return offset; + if (match_word(line, "new file mode")) + continue; + if (match_word(line, "deleted file mode")) + continue; + if (match_word(line, "copy")) + continue; + if (match_word(line, "rename")) + continue; + if (match_word(line, "similarity index")) + continue; + break; + } + + /* We want either a patch _or_ something real */ + return offset ? :-1; +} + +static int find_header(char *line, unsigned long size, int *hdrsize) +{ + unsigned long offset, len; + + for (offset = 0; size > 0; offset += len, size -= len, line += len) { + unsigned long nextlen; + + len = linelen(line, size); + if (!len) + break; + + /* Testing this early allows us to take a few shortcuts.. */ + if (len < 6) + continue; + if (size < len + 6) + break; + + /* + * Git patch? It might not have a real patch, just a rename + * or mode change, so we handle that specially + */ + if (!memcmp("diff --git ", line, 11)) { + int git_hdr_len = parse_git_header(line + len, size - len); + if (git_hdr_len < 0) + continue; + + *hdrsize = len + git_hdr_len; + return offset; + } + + /** --- followed by +++ ? */ + if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) + continue; + + /* + * We only accept unified patches, so we want it to + * at least have "@@ -a,b +c,d @@\n", which is 14 chars + * minimum + */ + nextlen = linelen(line + len, size - len); + if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) + continue; + + /* Ok, we'll consider it a patch */ + *hdrsize = len + nextlen; + return offset; + } + return -1; +} + +static int parse_num(const char *line, int len, int offset, const char *expect, unsigned long *p) +{ + char *ptr; + int digits, ex; + + if (offset < 0 || offset >= len) + return -1; + line += offset; + len -= offset; + + if (!isdigit(*line)) + return -1; + *p = strtoul(line, &ptr, 10); + + digits = ptr - line; + + offset += digits; + line += digits; + len -= digits; + + ex = strlen(expect); + if (ex > len) + return -1; + if (memcmp(line, expect, ex)) + return -1; + + return offset + ex; +} + +/* + * Parse a unified diff. Note that this really needs + * to parse each fragment separately, since the only + * way to know the difference between a "---" that is + * part of a patch, and a "---" that starts the next + * patch is to look at the line counts.. + */ +static int apply_fragment(char *line, unsigned long size) +{ + int len = linelen(line, size), offset; + unsigned long oldpos, oldlines, newpos, newlines; + + if (!len || line[len-1] != '\n') + return -1; + + /* Figure out the number of lines in a fragment */ + offset = parse_num(line, len, 4, ",", &oldpos); + offset = parse_num(line, len, offset, " +", &oldlines); + offset = parse_num(line, len, offset, ",", &newpos); + offset = parse_num(line, len, offset, " @@", &newlines); + if (offset < 0) + return -1; + + /* Parse the thing.. */ + line += len; + size -= len; + for (offset = len; size > 0; offset += len, size -= len, line += len) { + if (!oldlines && !newlines) + break; + len = linelen(line, size); + if (!len || line[len-1] != '\n') + return -1; + switch (*line) { + default: + return -1; + case ' ': + oldlines--; + newlines--; + break; + case '-': + oldlines--; + break; + case '+': + newlines--; + break; + } + } + return offset; +} + +static int apply_single_patch(char *line, unsigned long size) +{ + unsigned long offset = 0; + + while (size > 4 && !memcmp(line, "@@ -", 4)) { + int len = apply_fragment(line, size); + if (len <= 0) + break; + +printf("applying fragment:\n%.*s\n\n", len, line); + + offset += len; + line += len; + size -= len; + } + return offset; +} + +static int apply_chunk(char *buffer, unsigned long size) +{ + int hdrsize, patchsize; + int offset = find_header(buffer, size, &hdrsize); + char *header, *patch; + + if (offset < 0) + return offset; + header = buffer + offset; + +printf("Found header:\n%.*s\n\n", hdrsize, header); + + patch = header + hdrsize; + patchsize = apply_single_patch(patch, size - offset - hdrsize); + + return offset + hdrsize + patchsize; +} + +static int apply_patch(int fd) +{ + unsigned long offset, size; + char *buffer = read_patch_file(fd, &size); + + if (!buffer) + return -1; + offset = 0; + while (size > 0) { + int nr = apply_chunk(buffer + offset, size); + if (nr < 0) + break; + offset += nr; + size -= nr; + } + free(buffer); + return 0; +} + +int main(int argc, char **argv) +{ + int i; + + if (read_cache() < 0) + die("unable to read index file"); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + int fd; + + if (!strcmp(arg, "-")) { + apply_patch(0); + continue; + } + if (!strcmp(arg, "--no-merge")) { + merge_patch = 0; + continue; + } + fd = open(arg, O_RDONLY); + if (fd < 0) + usage(apply_usage); + apply_patch(fd); + close(fd); + } + return 0; +} |