Start implementing "git-apply"
authorLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 23 May 2005 17:52:17 +0000 (10:52 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 23 May 2005 17:52:17 +0000 (10:52 -0700)
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
apply.c [new file with mode: 0644]

index 04f7d78718b9d5cc8ad815d1bf4c4d1c1113d366..b957cec1a0d10c05e3ae09ab5460e914c0c367b6 100644 (file)
--- 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 (file)
index 0000000..85d7965
--- /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;
+}