apply.con commit git-apply: bad patch fragments are fatal (5e224a2)
   1/*
   2 * apply.c
   3 *
   4 * Copyright (C) Linus Torvalds, 2005
   5 *
   6 * This applies patches on top of some (arbitrary) version of the SCM.
   7 *
   8 * NOTE! It does all its work in the index file, and only cares about
   9 * the files in the working directory if you tell it to "merge" the
  10 * patch apply.
  11 *
  12 * Even when merging it always takes the source from the index, and
  13 * uses the working tree as a "branch" for a 3-way merge.
  14 */
  15#include <ctype.h>
  16
  17#include "cache.h"
  18
  19// We default to the merge behaviour, since that's what most people would
  20// expect
  21static int merge_patch = 1;
  22static const char apply_usage[] = "git-apply <patch>";
  23
  24#define CHUNKSIZE (8192)
  25
  26static void *read_patch_file(int fd, unsigned long *sizep)
  27{
  28        unsigned long size = 0, alloc = CHUNKSIZE;
  29        void *buffer = xmalloc(alloc);
  30
  31        for (;;) {
  32                int nr = alloc - size;
  33                if (nr < 1024) {
  34                        alloc += CHUNKSIZE;
  35                        buffer = xrealloc(buffer, alloc);
  36                        nr = alloc - size;
  37                }
  38                nr = read(fd, buffer + size, nr);
  39                if (!nr)
  40                        break;
  41                if (nr < 0) {
  42                        if (errno == EAGAIN)
  43                                continue;
  44                        die("git-apply: read returned %s", strerror(errno));
  45                }
  46                size += nr;
  47        }
  48        *sizep = size;
  49        return buffer;
  50}
  51
  52static unsigned long linelen(char *buffer, unsigned long size)
  53{
  54        unsigned long len = 0;
  55        while (size--) {
  56                len++;
  57                if (*buffer++ == '\n')
  58                        break;
  59        }
  60        return len;
  61}
  62
  63static int match_word(const char *line, const char *match)
  64{
  65        for (;;) {
  66                char c = *match++;
  67                if (!c)
  68                        break;
  69                if (*line++ != c)
  70                        return 0;
  71        }
  72        return *line == ' ';
  73}
  74
  75/* Verify that we recognize the lines following a git header */
  76static int parse_git_header(char *line, unsigned int size)
  77{
  78        unsigned long offset, len;
  79
  80        for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len) {
  81                len = linelen(line, size);
  82                if (!len)
  83                        break;
  84                if (line[len-1] != '\n')
  85                        return -1;
  86                if (len < 4)
  87                        break;
  88                if (!memcmp(line, "@@ -", 4))
  89                        return offset;
  90                if (match_word(line, "new file mode"))
  91                        continue;
  92                if (match_word(line, "deleted file mode"))
  93                        continue;
  94                if (match_word(line, "copy"))
  95                        continue;
  96                if (match_word(line, "rename"))
  97                        continue;
  98                if (match_word(line, "similarity index"))
  99                        continue;
 100                break;
 101        }
 102
 103        /* We want either a patch _or_ something real */
 104        return offset ? :-1;
 105}
 106
 107static int find_header(char *line, unsigned long size, int *hdrsize)
 108{
 109        unsigned long offset, len;
 110
 111        for (offset = 0; size > 0; offset += len, size -= len, line += len) {
 112                unsigned long nextlen;
 113
 114                len = linelen(line, size);
 115                if (!len)
 116                        break;
 117
 118                /* Testing this early allows us to take a few shortcuts.. */
 119                if (len < 6)
 120                        continue;
 121                if (size < len + 6)
 122                        break;
 123
 124                /*
 125                 * Git patch? It might not have a real patch, just a rename
 126                 * or mode change, so we handle that specially
 127                 */
 128                if (!memcmp("diff --git ", line, 11)) {
 129                        int git_hdr_len = parse_git_header(line + len, size - len);
 130                        if (git_hdr_len < 0)
 131                                continue;
 132
 133                        *hdrsize = len + git_hdr_len;
 134                        return offset;
 135                }
 136
 137                /** --- followed by +++ ? */
 138                if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
 139                        continue;
 140
 141                /*
 142                 * We only accept unified patches, so we want it to
 143                 * at least have "@@ -a,b +c,d @@\n", which is 14 chars
 144                 * minimum
 145                 */
 146                nextlen = linelen(line + len, size - len);
 147                if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
 148                        continue;
 149
 150                /* Ok, we'll consider it a patch */
 151                *hdrsize = len + nextlen;
 152                return offset;
 153        }
 154        return -1;
 155}
 156
 157static int parse_num(const char *line, int len, int offset, const char *expect, unsigned long *p)
 158{
 159        char *ptr;
 160        int digits, ex;
 161
 162        if (offset < 0 || offset >= len)
 163                return -1;
 164        line += offset;
 165        len -= offset;
 166
 167        if (!isdigit(*line))
 168                return -1;
 169        *p = strtoul(line, &ptr, 10);
 170
 171        digits = ptr - line;
 172
 173        offset += digits;
 174        line += digits;
 175        len -= digits;
 176
 177        ex = strlen(expect);
 178        if (ex > len)
 179                return -1;
 180        if (memcmp(line, expect, ex))
 181                return -1;
 182
 183        return offset + ex;
 184}
 185
 186/*
 187 * Parse a unified diff. Note that this really needs
 188 * to parse each fragment separately, since the only
 189 * way to know the difference between a "---" that is
 190 * part of a patch, and a "---" that starts the next
 191 * patch is to look at the line counts..
 192 */
 193static int apply_fragment(char *line, unsigned long size)
 194{
 195        int len = linelen(line, size), offset;
 196        unsigned long oldpos, oldlines, newpos, newlines;
 197
 198        if (!len || line[len-1] != '\n')
 199                return -1;
 200
 201        /* Figure out the number of lines in a fragment */
 202        offset = parse_num(line, len, 4, ",", &oldpos);
 203        offset = parse_num(line, len, offset, " +", &oldlines);
 204        offset = parse_num(line, len, offset, ",", &newpos);
 205        offset = parse_num(line, len, offset, " @@", &newlines);
 206        if (offset < 0)
 207                return -1;
 208
 209        /* Parse the thing.. */
 210        line += len;
 211        size -= len;
 212        for (offset = len; size > 0; offset += len, size -= len, line += len) {
 213                if (!oldlines && !newlines)
 214                        break;
 215                len = linelen(line, size);
 216                if (!len || line[len-1] != '\n')
 217                        return -1;
 218                switch (*line) {
 219                default:
 220                        return -1;
 221                case ' ':
 222                        oldlines--;
 223                        newlines--;
 224                        break;
 225                case '-':
 226                        oldlines--;
 227                        break;
 228                case '+':
 229                        newlines--;
 230                        break;
 231                }
 232        }
 233        return offset;
 234}
 235
 236static int apply_single_patch(char *line, unsigned long size)
 237{
 238        unsigned long offset = 0;
 239
 240        while (size > 4 && !memcmp(line, "@@ -", 4)) {
 241                int len = apply_fragment(line, size);
 242                if (len <= 0)
 243                        die("corrupt patch");
 244
 245printf("applying fragment:\n%.*s\n\n", len, line);
 246
 247                offset += len;
 248                line += len;
 249                size -= len;
 250        }
 251        return offset;
 252}
 253
 254static int apply_chunk(char *buffer, unsigned long size)
 255{
 256        int hdrsize, patchsize;
 257        int offset = find_header(buffer, size, &hdrsize);
 258        char *header, *patch;
 259
 260        if (offset < 0)
 261                return offset;
 262        header = buffer + offset;
 263
 264printf("Found header:\n%.*s\n\n", hdrsize, header);
 265
 266        patch = header + hdrsize;
 267        patchsize = apply_single_patch(patch, size - offset - hdrsize);
 268
 269        return offset + hdrsize + patchsize;
 270}
 271
 272static int apply_patch(int fd)
 273{
 274        unsigned long offset, size;
 275        char *buffer = read_patch_file(fd, &size);
 276
 277        if (!buffer)
 278                return -1;
 279        offset = 0;
 280        while (size > 0) {
 281                int nr = apply_chunk(buffer + offset, size);
 282                if (nr < 0)
 283                        break;
 284                offset += nr;
 285                size -= nr;
 286        }
 287        free(buffer);
 288        return 0;
 289}
 290
 291int main(int argc, char **argv)
 292{
 293        int i;
 294
 295        if (read_cache() < 0)
 296                die("unable to read index file");
 297
 298        for (i = 1; i < argc; i++) {
 299                const char *arg = argv[i];
 300                int fd;
 301
 302                if (!strcmp(arg, "-")) {
 303                        apply_patch(0);
 304                        continue;
 305                }
 306                if (!strcmp(arg, "--no-merge")) {
 307                        merge_patch = 0;
 308                        continue;
 309                }
 310                fd = open(arg, O_RDONLY);
 311                if (fd < 0)
 312                        usage(apply_usage);
 313                apply_patch(fd);
 314                close(fd);
 315        }
 316        return 0;
 317}