Merge branch 'jc/status'
[gitweb.git] / apply.c
diff --git a/apply.c b/apply.c
index c58d9a249135872c7df13e2b99c24fde355367ca..35ae48eeb580a95399fd213c5813fc0fa4ec40c2 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -5,36 +5,34 @@
  *
  * 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 <fnmatch.h>
 #include "cache.h"
 #include "quote.h"
 
-// We default to the merge behaviour, since that's what most people would
-// expect.
-//
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
 //  --stat does just a diffstat, and doesn't actually apply
-//  --show-files shows the directory changes
+//  --numstat does numeric diffstat, and doesn't actually apply
+//  --index-info shows the old and new index info for paths if available.
 //
-static int merge_patch = 1;
+static const char *prefix;
+static int prefix_length = -1;
+
+static int p_value = 1;
+static int allow_binary_replacement = 0;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
+static int numstat = 0;
 static int summary = 0;
 static int check = 0;
 static int apply = 1;
-static int show_files = 0;
+static int no_add = 0;
+static int show_index_info = 0;
+static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -61,12 +59,14 @@ struct fragment {
 struct patch {
        char *new_name, *old_name, *def_name;
        unsigned int old_mode, new_mode;
-       int is_rename, is_copy, is_new, is_delete;
+       int is_rename, is_copy, is_new, is_delete, is_binary;
        int lines_added, lines_deleted;
        int score;
        struct fragment *fragments;
        char *result;
        unsigned long resultsize;
+       char old_sha1_prefix[41];
+       char new_sha1_prefix[41];
        struct patch *next;
 };
 
@@ -85,14 +85,11 @@ static void *read_patch_file(int fd, unsigned long *sizep)
                        buffer = xrealloc(buffer, alloc);
                        nr = alloc - size;
                }
-               nr = read(fd, buffer + size, nr);
+               nr = xread(fd, buffer + size, nr);
                if (!nr)
                        break;
-               if (nr < 0) {
-                       if (errno == EAGAIN)
-                               continue;
+               if (nr < 0)
                        die("git-apply: read returned %s", strerror(errno));
-               }
                size += nr;
        }
        *sizep = size;
@@ -221,7 +218,6 @@ static char * find_name(const char *line, char *def, int p_value, int terminate)
  */
 static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
 {
-       int p_value = 1;
        char *name;
 
        first += 4;     // skip "--- "
@@ -366,6 +362,38 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch)
        return 0;
 }
 
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+       /* index line is N hexadecimal, "..", N hexadecimal,
+        * and optional space with octal mode.
+        */
+       const char *ptr, *eol;
+       int len;
+
+       ptr = strchr(line, '.');
+       if (!ptr || ptr[1] != '.' || 40 < ptr - line)
+               return 0;
+       len = ptr - line;
+       memcpy(patch->old_sha1_prefix, line, len);
+       patch->old_sha1_prefix[len] = 0;
+
+       line = ptr + 2;
+       ptr = strchr(line, ' ');
+       eol = strchr(line, '\n');
+
+       if (!ptr || eol < ptr)
+               ptr = eol;
+       len = ptr - line;
+
+       if (40 < len)
+               return 0;
+       memcpy(patch->new_sha1_prefix, line, len);
+       patch->new_sha1_prefix[len] = 0;
+       if (*ptr == ' ')
+               patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8);
+       return 0;
+}
+
 /*
  * This is normal for a diff that doesn't change anything: we'll fall through
  * into the next diff. Tell the parser to break out.
@@ -565,6 +593,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                        { "rename to ", gitdiff_renamedst },
                        { "similarity index ", gitdiff_similarity },
                        { "dissimilarity index ", gitdiff_dissimilarity },
+                       { "index ", gitdiff_index },
                        { "", gitdiff_unrecognized },
                };
                int i;
@@ -793,7 +822,10 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                 /* We allow "\ No newline at end of file". Depending
                  * on locale settings when the patch was produced we
                  * don't know what this line looks like. The only
-                 * thing we do know is that it begins with "\ ". */
+                 * thing we do know is that it begins with "\ ".
+                * Checking for 12 is just for sanity check -- any
+                * l10n of "\ No newline..." is at least that long.
+                */
                case '\\':
                        if (len < 12 || memcmp(line, "\\ ", 2))
                                return -1;
@@ -860,8 +892,36 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 
        patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
 
-       if (!patchsize && !metadata_changes(patch))
-               die("patch with only garbage at line %d", linenr);
+       if (!patchsize) {
+               static const char *binhdr[] = {
+                       "Binary files ",
+                       "Files ",
+                       NULL,
+               };
+               int i;
+               int hd = hdrsize + offset;
+               unsigned long llen = linelen(buffer + hd, size - hd);
+
+               if (!memcmp(" differ\n", buffer + hd + llen - 8, 8))
+                       for (i = 0; binhdr[i]; i++) {
+                               int len = strlen(binhdr[i]);
+                               if (len < size - hd &&
+                                   !memcmp(binhdr[i], buffer + hd, len)) {
+                                       patch->is_binary = 1;
+                                       break;
+                               }
+                       }
+
+               /* Empty patch cannot be applied if:
+                * - it is a binary patch and we do not do binary_replace, or
+                * - text patch without metadata change
+                */
+               if ((apply || check) &&
+                   (patch->is_binary
+                    ? !allow_binary_replacement
+                    : !metadata_changes(patch)))
+                       die("patch with only garbage at line %d", linenr);
+       }
 
        return offset + hdrsize + patchsize;
 }
@@ -919,9 +979,12 @@ static void show_stats(struct patch *patch)
                add = (add * max + max_change / 2) / max_change;
                del = total - add;
        }
-       printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
-               len, name, patch->lines_added + patch->lines_deleted,
-               add, pluses, del, minuses);
+       if (patch->is_binary)
+               printf(" %s%-*s |  Bin\n", prefix, len, name);
+       else
+               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+                      len, name, patch->lines_added + patch->lines_deleted,
+                      add, pluses, del, minuses);
        if (qname)
                free(qname);
 }
@@ -940,13 +1003,8 @@ static int read_old_data(struct stat *st, const char *path, void *buf, unsigned
                        return error("unable to open %s", path);
                got = 0;
                for (;;) {
-                       int ret = read(fd, buf + got, size - got);
-                       if (ret < 0) {
-                               if (errno == EAGAIN)
-                                       continue;
-                               break;
-                       }
-                       if (!ret)
+                       int ret = xread(fd, buf + got, size - got);
+                       if (ret <= 0)
                                break;
                        got += ret;
                }
@@ -1069,8 +1127,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
                                break;
                /* Fall-through for ' ' */
                case '+':
-                       memcpy(new + newsize, patch + 1, plen);
-                       newsize += plen;
+                       if (*patch != '+' || !no_add) {
+                               memcpy(new + newsize, patch + 1, plen);
+                               newsize += plen;
+                       }
                        break;
                case '@': case '\\':
                        /* Ignore it, we already handled it */
@@ -1108,10 +1168,77 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
 static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
 {
        struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+       if (patch->is_binary) {
+               unsigned char sha1[20];
+
+               if (!allow_binary_replacement)
+                       return error("cannot apply binary patch to '%s' "
+                                    "without --allow-binary-replacement",
+                                    name);
+
+               /* For safety, we require patch index line to contain
+                * full 40-byte textual SHA1 for old and new, at least for now.
+                */
+               if (strlen(patch->old_sha1_prefix) != 40 ||
+                   strlen(patch->new_sha1_prefix) != 40 ||
+                   get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+                   get_sha1_hex(patch->new_sha1_prefix, sha1))
+                       return error("cannot apply binary patch to '%s' "
+                                    "without full index line", name);
+
+               if (patch->old_name) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+
+                       /* See if the old one matches what the patch
+                        * applies to.
+                        */
+                       write_sha1_file_prepare(desc->buffer, desc->size,
+                                               "blob", sha1, hdr, &hdrlen);
+                       if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+                               return error("the patch applies to '%s' (%s), "
+                                            "which does not match the "
+                                            "current contents.",
+                                            name, sha1_to_hex(sha1));
+               }
+               else {
+                       /* Otherwise, the old one must be empty. */
+                       if (desc->size)
+                               return error("the patch applies to an empty "
+                                            "'%s' but it is not empty", name);
+               }
+
+               /* For now, we do not record post-image data in the patch,
+                * and require the object already present in the recipient's
+                * object database.
+                */
+               if (desc->buffer) {
+                       free(desc->buffer);
+                       desc->alloc = desc->size = 0;
+               }
+               get_sha1_hex(patch->new_sha1_prefix, sha1);
+
+               if (memcmp(sha1, null_sha1, 20)) {
+                       char type[10];
+                       unsigned long size;
+
+                       desc->buffer = read_sha1_file(sha1, type, &size);
+                       if (!desc->buffer)
+                               return error("the necessary postimage %s for "
+                                            "'%s' does not exist",
+                                            patch->new_sha1_prefix, name);
+                       desc->alloc = desc->size = size;
+               }
+
+               return 0;
+       }
 
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
-                       return error("patch failed: %s:%ld", patch->old_name, frag->oldpos);
+                       return error("patch failed: %s:%ld",
+                                    name, frag->oldpos);
                frag = frag->next;
        }
        return 0;
@@ -1153,20 +1280,43 @@ static int check_patch(struct patch *patch)
        struct stat st;
        const char *old_name = patch->old_name;
        const char *new_name = patch->new_name;
+       const char *name = old_name ? old_name : new_name;
 
        if (old_name) {
                int changed;
+               int stat_ret = lstat(old_name, &st);
 
-               if (lstat(old_name, &st) < 0)
-                       return error("%s: %s", old_name, strerror(errno));
                if (check_index) {
                        int pos = cache_name_pos(old_name, strlen(old_name));
                        if (pos < 0)
-                               return error("%s: does not exist in index", old_name);
-                       changed = ce_match_stat(active_cache[pos], &st);
+                               return error("%s: does not exist in index",
+                                            old_name);
+                       if (stat_ret < 0) {
+                               struct checkout costate;
+                               if (errno != ENOENT)
+                                       return error("%s: %s", old_name,
+                                                    strerror(errno));
+                               /* checkout */
+                               costate.base_dir = "";
+                               costate.base_dir_len = 0;
+                               costate.force = 0;
+                               costate.quiet = 0;
+                               costate.not_new = 0;
+                               costate.refresh_cache = 1;
+                               if (checkout_entry(active_cache[pos],
+                                                  &costate) ||
+                                   lstat(old_name, &st))
+                                       return -1;
+                       }
+
+                       changed = ce_match_stat(active_cache[pos], &st, 1);
                        if (changed)
-                               return error("%s: does not match index", old_name);
+                               return error("%s: does not match index",
+                                            old_name);
                }
+               else if (stat_ret < 0)
+                       return error("%s: %s", old_name, strerror(errno));
+
                if (patch->is_new < 0)
                        patch->is_new = 0;
                st.st_mode = ntohl(create_ce_mode(st.st_mode));
@@ -1205,7 +1355,7 @@ static int check_patch(struct patch *patch)
        }       
 
        if (apply_data(patch, &st) < 0)
-               return error("%s: patch does not apply", old_name);
+               return error("%s: patch does not apply", name);
        return 0;
 }
 
@@ -1218,32 +1368,38 @@ static int check_patch_list(struct patch *patch)
        return error;
 }
 
-static void show_file(int c, unsigned int mode, const char *name)
+static inline int is_null_sha1(const unsigned char *sha1)
 {
-       printf("%c %o %s\n", c, mode, name);
+       return !memcmp(sha1, null_sha1, 20);
 }
 
-static void show_file_list(struct patch *patch)
+static void show_index_list(struct patch *list)
 {
-       for (;patch ; patch = patch->next) {
-               if (patch->is_rename) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_copy || patch->is_new) {
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_delete) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       continue;
-               }
-               if (patch->old_mode && patch->new_mode && patch->old_mode != patch->new_mode) {
-                       printf("M %o:%o %s\n", patch->old_mode, patch->new_mode, patch->old_name);
-                       continue;
-               }
-               printf("M %o %s\n", patch->old_mode, patch->old_name);
+       struct patch *patch;
+
+       /* Once we start supporting the reverse patch, it may be
+        * worth showing the new sha1 prefix, but until then...
+        */
+       for (patch = list; patch; patch = patch->next) {
+               const unsigned char *sha1_ptr;
+               unsigned char sha1[20];
+               const char *name;
+
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               if (patch->is_new)
+                       sha1_ptr = null_sha1;
+               else if (get_sha1(patch->old_sha1_prefix, sha1))
+                       die("sha1 information is lacking or useless (%s).",
+                           name);
+               else
+                       sha1_ptr = sha1;
+
+               printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar(line_termination);
        }
 }
 
@@ -1261,6 +1417,20 @@ static void stat_patch_list(struct patch *patch)
        printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 }
 
+static void numstat_patch_list(struct patch *patch)
+{
+       for ( ; patch; patch = patch->next) {
+               const char *name;
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar('\n');
+       }
+}
+
 static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 {
        if (mode)
@@ -1394,40 +1564,19 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
                die("unable to add cache entry for %s", path);
 }
 
-static void create_subdirectories(const char *path)
-{
-       int len = strlen(path);
-       char *buf = xmalloc(len + 1);
-       const char *slash = path;
-
-       while ((slash = strchr(slash+1, '/')) != NULL) {
-               len = slash - path;
-               memcpy(buf, path, len);
-               buf[len] = 0;
-               if (mkdir(buf, 0777) < 0) {
-                       if (errno != EEXIST)
-                               break;
-               }
-       }
-       free(buf);
-}
-
 static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
 {
        int fd;
 
        if (S_ISLNK(mode))
                return symlink(buf, path);
-       fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC, (mode & 0100) ? 0777 : 0666);
+       fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
        if (fd < 0)
                return -1;
        while (size) {
-               int written = write(fd, buf, size);
-               if (written < 0) {
-                       if (errno == EINTR || errno == EAGAIN)
-                               continue;
+               int written = xwrite(fd, buf, size);
+               if (written < 0)
                        die("writing file %s: %s", path, strerror(errno));
-               }
                if (!written)
                        die("out of space writing file %s", path);
                buf += written;
@@ -1443,13 +1592,14 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
  * which is true 99% of the time anyway. If they don't,
  * we create them and try again.
  */
-static void create_one_file(const char *path, unsigned mode, const char *buf, unsigned long size)
+static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
 {
        if (!try_create_file(path, mode, buf, size))
                return;
 
        if (errno == ENOENT) {
-               create_subdirectories(path);
+               if (safe_create_leading_directories(path))
+                       return;
                if (!try_create_file(path, mode, buf, size))
                        return;
        }
@@ -1468,14 +1618,15 @@ static void create_one_file(const char *path, unsigned mode, const char *buf, un
                        }
                        if (errno != EEXIST)
                                break;
-               }                       
+                       ++nr;
+               }
        }
        die("unable to write file %s mode %o", path, mode);
 }
 
 static void create_file(struct patch *patch)
 {
-       const char *path = patch->new_name;
+       char *path = patch->new_name;
        unsigned mode = patch->new_mode;
        unsigned long size = patch->resultsize;
        char *buf = patch->result;
@@ -1531,6 +1682,12 @@ static int use_patch(struct patch *p)
                        return 0;
                x = x->next;
        }
+       if (0 < prefix_length) {
+               int pathlen = strlen(pathname);
+               if (pathlen <= prefix_length ||
+                   memcmp(prefix, pathname, prefix_length))
+                       return 0;
+       }
        return 1;
 }
 
@@ -1588,12 +1745,15 @@ static int apply_patch(int fd)
                        die("Unable to write new cachefile");
        }
 
-       if (show_files)
-               show_file_list(list);
+       if (show_index_info)
+               show_index_list(list);
 
        if (diffstat)
                stat_patch_list(list);
 
+       if (numstat)
+               numstat_patch_list(list);
+
        if (summary)
                summary_patch_list(list);
 
@@ -1622,9 +1782,12 @@ int main(int argc, char **argv)
                        excludes = x;
                        continue;
                }
-               /* NEEDSWORK: this does not do anything at this moment. */
-               if (!strcmp(arg, "--no-merge")) {
-                       merge_patch = 0;
+               if (!strncmp(arg, "-p", 2)) {
+                       p_value = atoi(arg + 2);
+                       continue;
+               }
+               if (!strcmp(arg, "--no-add")) {
+                       no_add = 1;
                        continue;
                }
                if (!strcmp(arg, "--stat")) {
@@ -1632,6 +1795,15 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--allow-binary-replacement")) {
+                       allow_binary_replacement = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--numstat")) {
+                       apply = 0;
+                       numstat = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--summary")) {
                        apply = 0;
                        summary = 1;
@@ -1650,10 +1822,24 @@ int main(int argc, char **argv)
                        apply = 1;
                        continue;
                }
-               if (!strcmp(arg, "--show-files")) {
-                       show_files = 1;
+               if (!strcmp(arg, "--index-info")) {
+                       apply = 0;
+                       show_index_info = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
                        continue;
                }
+
+               if (check_index && prefix_length < 0) {
+                       prefix = setup_git_directory();
+                       prefix_length = prefix ? strlen(prefix) : 0;
+                       git_config(git_default_config);
+               }
+               if (0 < prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+
                fd = open(arg, O_RDONLY);
                if (fd < 0)
                        usage(apply_usage);