apply.con commit git-apply: improve error detection and messages (46979f5)
   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
  24static int linenr = 1;
  25
  26#define CHUNKSIZE (8192)
  27
  28static void *read_patch_file(int fd, unsigned long *sizep)
  29{
  30        unsigned long size = 0, alloc = CHUNKSIZE;
  31        void *buffer = xmalloc(alloc);
  32
  33        for (;;) {
  34                int nr = alloc - size;
  35                if (nr < 1024) {
  36                        alloc += CHUNKSIZE;
  37                        buffer = xrealloc(buffer, alloc);
  38                        nr = alloc - size;
  39                }
  40                nr = read(fd, buffer + size, nr);
  41                if (!nr)
  42                        break;
  43                if (nr < 0) {
  44                        if (errno == EAGAIN)
  45                                continue;
  46                        die("git-apply: read returned %s", strerror(errno));
  47                }
  48                size += nr;
  49        }
  50        *sizep = size;
  51        return buffer;
  52}
  53
  54static unsigned long linelen(char *buffer, unsigned long size)
  55{
  56        unsigned long len = 0;
  57        while (size--) {
  58                len++;
  59                if (*buffer++ == '\n')
  60                        break;
  61        }
  62        return len;
  63}
  64
  65static int match_word(const char *line, const char *match)
  66{
  67        for (;;) {
  68                char c = *match++;
  69                if (!c)
  70                        break;
  71                if (*line++ != c)
  72                        return 0;
  73        }
  74        return *line == ' ';
  75}
  76
  77/* Verify that we recognize the lines following a git header */
  78static int parse_git_header(char *line, unsigned int size)
  79{
  80        unsigned long offset, len;
  81
  82        for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
  83                len = linelen(line, size);
  84                if (!len)
  85                        break;
  86                if (line[len-1] != '\n')
  87                        return -1;
  88                if (len < 4)
  89                        break;
  90                if (!memcmp(line, "@@ -", 4))
  91                        return offset;
  92                if (match_word(line, "new file mode"))
  93                        continue;
  94                if (match_word(line, "deleted file mode"))
  95                        continue;
  96                if (match_word(line, "copy"))
  97                        continue;
  98                if (match_word(line, "rename"))
  99                        continue;
 100                if (match_word(line, "similarity index"))
 101                        continue;
 102                break;
 103        }
 104
 105        /* We want either a patch _or_ something real */
 106        return offset ? :-1;
 107}
 108
 109static int parse_num(const char *line, int len, int offset, const char *expect, unsigned long *p)
 110{
 111        char *ptr;
 112        int digits, ex;
 113
 114        if (offset < 0 || offset >= len)
 115                return -1;
 116        line += offset;
 117        len -= offset;
 118
 119        if (!isdigit(*line))
 120                return -1;
 121        *p = strtoul(line, &ptr, 10);
 122
 123        digits = ptr - line;
 124
 125        offset += digits;
 126        line += digits;
 127        len -= digits;
 128
 129        ex = strlen(expect);
 130        if (ex > len)
 131                return -1;
 132        if (memcmp(line, expect, ex))
 133                return -1;
 134
 135        return offset + ex;
 136}
 137
 138/*
 139 * Parse a unified diff fragment header of the
 140 * form "@@ -a,b +c,d @@"
 141 */
 142static int parse_fragment_header(char *line, int len, unsigned long *pos)
 143{
 144        int offset;
 145
 146        if (!len || line[len-1] != '\n')
 147                return -1;
 148
 149        /* Figure out the number of lines in a fragment */
 150        offset = parse_num(line, len, 4, ",", pos);
 151        offset = parse_num(line, len, offset, " +", pos+1);
 152        offset = parse_num(line, len, offset, ",", pos+2);
 153        offset = parse_num(line, len, offset, " @@", pos+3);
 154
 155        return offset;
 156}
 157
 158static int find_header(char *line, unsigned long size, int *hdrsize)
 159{
 160        unsigned long offset, len;
 161
 162        for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
 163                unsigned long nextlen;
 164
 165                len = linelen(line, size);
 166                if (!len)
 167                        break;
 168
 169                /* Testing this early allows us to take a few shortcuts.. */
 170                if (len < 6)
 171                        continue;
 172
 173                /*
 174                 * Make sure we don't find any unconnected patch fragmants.
 175                 * That's a sign that we didn't find a header, and that a
 176                 * patch has become corrupted/broken up.
 177                 */
 178                if (!memcmp("@@ -", line, 4)) {
 179                        unsigned long pos[4];
 180                        if (parse_fragment_header(line, len, pos) < 0)
 181                                continue;
 182                        error("patch fragment without header at line %d: %.*s", linenr, len-1, line);
 183                }
 184
 185                if (size < len + 6)
 186                        break;
 187
 188                /*
 189                 * Git patch? It might not have a real patch, just a rename
 190                 * or mode change, so we handle that specially
 191                 */
 192                if (!memcmp("diff --git ", line, 11)) {
 193                        int git_hdr_len = parse_git_header(line + len, size - len);
 194                        if (git_hdr_len < 0)
 195                                continue;
 196
 197                        *hdrsize = len + git_hdr_len;
 198                        return offset;
 199                }
 200
 201                /** --- followed by +++ ? */
 202                if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
 203                        continue;
 204
 205                /*
 206                 * We only accept unified patches, so we want it to
 207                 * at least have "@@ -a,b +c,d @@\n", which is 14 chars
 208                 * minimum
 209                 */
 210                nextlen = linelen(line + len, size - len);
 211                if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
 212                        continue;
 213
 214                /* Ok, we'll consider it a patch */
 215                *hdrsize = len + nextlen;
 216                linenr += 2;
 217                return offset;
 218        }
 219        return -1;
 220}
 221
 222/*
 223 * Parse a unified diff. Note that this really needs
 224 * to parse each fragment separately, since the only
 225 * way to know the difference between a "---" that is
 226 * part of a patch, and a "---" that starts the next
 227 * patch is to look at the line counts..
 228 */
 229static int apply_fragment(char *line, unsigned long size)
 230{
 231        int len = linelen(line, size), offset;
 232        unsigned long pos[4], oldlines, newlines;
 233
 234        offset = parse_fragment_header(line, len, pos);
 235        if (offset < 0)
 236                return -1;
 237        oldlines = pos[1];
 238        newlines = pos[3];
 239
 240        /* Parse the thing.. */
 241        line += len;
 242        size -= len;
 243        linenr++;
 244        for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
 245                if (!oldlines && !newlines)
 246                        break;
 247                len = linelen(line, size);
 248                if (!len || line[len-1] != '\n')
 249                        return -1;
 250                switch (*line) {
 251                default:
 252                        return -1;
 253                case ' ':
 254                        oldlines--;
 255                        newlines--;
 256                        break;
 257                case '-':
 258                        oldlines--;
 259                        break;
 260                case '+':
 261                        newlines--;
 262                        break;
 263                }
 264        }
 265        return offset;
 266}
 267
 268static int apply_single_patch(char *line, unsigned long size)
 269{
 270        unsigned long offset = 0;
 271
 272        while (size > 4 && !memcmp(line, "@@ -", 4)) {
 273                int len = apply_fragment(line, size);
 274                if (len <= 0)
 275                        die("corrupt patch at line %d", linenr);
 276
 277printf("applying fragment:\n%.*s\n\n", len, line);
 278
 279                offset += len;
 280                line += len;
 281                size -= len;
 282        }
 283        return offset;
 284}
 285
 286static int apply_chunk(char *buffer, unsigned long size)
 287{
 288        int hdrsize, patchsize;
 289        int offset = find_header(buffer, size, &hdrsize);
 290        char *header, *patch;
 291
 292        if (offset < 0)
 293                return offset;
 294        header = buffer + offset;
 295
 296printf("Found header:\n%.*s\n\n", hdrsize, header);
 297
 298        patch = header + hdrsize;
 299        patchsize = apply_single_patch(patch, size - offset - hdrsize);
 300
 301        return offset + hdrsize + patchsize;
 302}
 303
 304static int apply_patch(int fd)
 305{
 306        unsigned long offset, size;
 307        char *buffer = read_patch_file(fd, &size);
 308
 309        if (!buffer)
 310                return -1;
 311        offset = 0;
 312        while (size > 0) {
 313                int nr = apply_chunk(buffer + offset, size);
 314                if (nr < 0)
 315                        break;
 316                offset += nr;
 317                size -= nr;
 318        }
 319        free(buffer);
 320        return 0;
 321}
 322
 323int main(int argc, char **argv)
 324{
 325        int i;
 326
 327        if (read_cache() < 0)
 328                die("unable to read index file");
 329
 330        for (i = 1; i < argc; i++) {
 331                const char *arg = argv[i];
 332                int fd;
 333
 334                if (!strcmp(arg, "-")) {
 335                        apply_patch(0);
 336                        continue;
 337                }
 338                if (!strcmp(arg, "--no-merge")) {
 339                        merge_patch = 0;
 340                        continue;
 341                }
 342                fd = open(arg, O_RDONLY);
 343                if (fd < 0)
 344                        usage(apply_usage);
 345                apply_patch(fd);
 346                close(fd);
 347        }
 348        return 0;
 349}