From c1bb93502012b710ec5a31dff62d4aca7bf51adc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 23 May 2005 10:52:17 -0700 Subject: [PATCH] Start implementing "git-apply" This applies git patches (and old-style unified diffs) in the index, rather than doing it in the working directory. That allows for a lot more flexibility, and means that if a patch fails, we aren't going to mess up the working directory. NOTE! This is just the first cut at it, and right now it only parses the incoming patch, it doesn't actually apply it yet. --- Makefile | 2 +- apply.c | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 apply.c diff --git a/Makefile b/Makefile index 04f7d7871..b957cec1a 100644 --- a/Makefile +++ b/Makefile @@ -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 + +#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 "; + +#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; +} -- 2.26.2