aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--apply.c317
2 files changed, 318 insertions, 1 deletions
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 <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;
+}