Make git-repack use git-pack-intersect.
[gitweb.git] / apply.c
diff --git a/apply.c b/apply.c
index c671d9e86cc04e2c6756212e3496c4ef5db8207a..34181188b890df19c8a5c96a1c1eec21db4805c1 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -5,35 +5,29 @@
  *
  * 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 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] [-z] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -60,12 +54,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;
 };
 
@@ -142,6 +138,35 @@ static char * find_name(const char *line, char *def, int p_value, int terminate)
        const char *start = line;
        char *name;
 
+       if (*line == '"') {
+               /* Proposed "new-style" GNU patch/diff format; see
+                * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+                */
+               name = unquote_c_style(line, NULL);
+               if (name) {
+                       char *cp = name;
+                       while (p_value) {
+                               cp = strchr(name, '/');
+                               if (!cp)
+                                       break;
+                               cp++;
+                               p_value--;
+                       }
+                       if (cp) {
+                               /* name can later be freed, so we need
+                                * to memmove, not just return cp
+                                */
+                               memmove(name, cp, strlen(cp) + 1);
+                               free(def);
+                               return name;
+                       }
+                       else {
+                               free(name);
+                               name = NULL;
+                       }
+               }
+       }
+
        for (;;) {
                char c = *line;
 
@@ -231,37 +256,29 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
  */
 static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
 {
-       int len;
-       const char *name;
-
        if (!orig_name && !isnull)
                return find_name(line, NULL, 1, 0);
 
-       name = "/dev/null";
-       len = 9;
        if (orig_name) {
+               int len;
+               const char *name;
+               char *another;
                name = orig_name;
                len = strlen(name);
                if (isnull)
                        die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
-       }
-
-       if (*name == '/')
-               goto absolute_path;
-
-       for (;;) {
-               char c = *line++;
-               if (c == '\n')
-                       break;
-               if (c != '/')
-                       continue;
-absolute_path:
-               if (memcmp(line, name, len) || line[len] != '\n')
-                       break;
+               another = find_name(line, NULL, 1, 0);
+               if (!another || memcmp(another, name, len))
+                       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+               free(another);
                return orig_name;
        }
-       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
-       return NULL;
+       else {
+               /* expect "/dev/null" */
+               if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+                       die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
+               return NULL;
+       }
 }
 
 static int gitdiff_oldname(const char *line, struct patch *patch)
@@ -344,6 +361,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.
@@ -353,29 +402,124 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
        return -1;
 }
 
-static char *git_header_name(char *line)
+static const char *stop_at_slash(const char *line, int llen)
+{
+       int i;
+
+       for (i = 0; i < llen; i++) {
+               int ch = line[i];
+               if (ch == '/')
+                       return line + i;
+       }
+       return NULL;
+}
+
+/* This is to extract the same name that appears on "diff --git"
+ * line.  We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file.  In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
 {
        int len;
-       char *name, *second;
+       const char *name;
+       const char *second = NULL;
 
-       /*
-        * Find the first '/'
-        */
-       name = line;
-       for (;;) {
-               char c = *name++;
-               if (c == '\n')
+       line += strlen("diff --git ");
+       llen -= strlen("diff --git ");
+
+       if (*line == '"') {
+               const char *cp;
+               char *first = unquote_c_style(line, &second);
+               if (!first)
                        return NULL;
-               if (c == '/')
-                       break;
+
+               /* advance to the first slash */
+               cp = stop_at_slash(first, strlen(first));
+               if (!cp || cp == first) {
+                       /* we do not accept absolute paths */
+               free_first_and_fail:
+                       free(first);
+                       return NULL;
+               }
+               len = strlen(cp+1);
+               memmove(first, cp+1, len+1); /* including NUL */
+
+               /* second points at one past closing dq of name.
+                * find the second name.
+                */
+               while ((second < line + llen) && isspace(*second))
+                       second++;
+
+               if (line + llen <= second)
+                       goto free_first_and_fail;
+               if (*second == '"') {
+                       char *sp = unquote_c_style(second, NULL);
+                       if (!sp)
+                               goto free_first_and_fail;
+                       cp = stop_at_slash(sp, strlen(sp));
+                       if (!cp || cp == sp) {
+                       free_both_and_fail:
+                               free(sp);
+                               goto free_first_and_fail;
+                       }
+                       /* They must match, otherwise ignore */
+                       if (strcmp(cp+1, first))
+                               goto free_both_and_fail;
+                       free(sp);
+                       return first;
+               }
+
+               /* unquoted second */
+               cp = stop_at_slash(second, line + llen - second);
+               if (!cp || cp == second)
+                       goto free_first_and_fail;
+               cp++;
+               if (line + llen - cp != len + 1 ||
+                   memcmp(first, cp, len))
+                       goto free_first_and_fail;
+               return first;
        }
 
-       /*
-        * We don't accept absolute paths (/dev/null) as possibly valid
-        */
-       if (name == line+1)
+       /* unquoted first name */
+       name = stop_at_slash(line, llen);
+       if (!name || name == line)
                return NULL;
 
+       name++;
+
+       /* since the first name is unquoted, a dq if exists must be
+        * the beginning of the second name.
+        */
+       for (second = name; second < line + llen; second++) {
+               if (*second == '"') {
+                       const char *cp = second;
+                       const char *np;
+                       char *sp = unquote_c_style(second, NULL);
+
+                       if (!sp)
+                               return NULL;
+                       np = stop_at_slash(sp, strlen(sp));
+                       if (!np || np == sp) {
+                       free_second_and_fail:
+                               free(sp);
+                               return NULL;
+                       }
+                       np++;
+                       len = strlen(np);
+                       if (len < cp - name &&
+                           !strncmp(np, name, len) &&
+                           isspace(name[len])) {
+                               /* Good */
+                               memmove(sp, np, len + 1);
+                               return sp;
+                       }
+                       goto free_second_and_fail;
+               }
+       }
+
        /*
         * Accept a name only if it shows up twice, exactly the same
         * form.
@@ -387,7 +531,7 @@ static char *git_header_name(char *line)
                default:
                        continue;
                case '\n':
-                       break;
+                       return NULL;
                case '\t': case ' ':
                        second = name+len;
                        for (;;) {
@@ -423,7 +567,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
         * or removing or adding empty files), so we get
         * the default name from the header.
         */
-       patch->def_name = git_header_name(line + strlen("diff --git "));
+       patch->def_name = git_header_name(line, len);
 
        line += len;
        size -= len;
@@ -448,6 +592,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;
@@ -563,7 +708,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                        struct fragment dummy;
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
-                       error("patch fragment without header at line %d: %.*s", linenr, len-1, line);
+                       error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
                }
 
                if (size < len + 6)
@@ -672,9 +817,16 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                        added++;
                        newlines--;
                        break;
-               /* We allow "\ No newline at end of file" */
+
+                /* 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 "\ ".
+                * Checking for 12 is just for sanity check -- any
+                * l10n of "\ No newline..." is at least that long.
+                */
                case '\\':
-                       if (len < 12 || memcmp(line, "\\ No newline", 12))
+                       if (len < 12 || memcmp(line, "\\ ", 2))
                                return -1;
                        break;
                }
@@ -683,7 +835,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
         * it in the above loop because we hit oldlines == newlines == 0
         * before seeing it.
         */
-       if (12 < size && !memcmp(line, "\\ No newline", 12))
+       if (12 < size && !memcmp(line, "\\ ", 2))
                offset += linelen(line, size);
 
        patch->lines_added += added;
@@ -719,6 +871,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
        return offset;
 }
 
+static inline int metadata_changes(struct patch *patch)
+{
+       return  patch->is_rename > 0 ||
+               patch->is_copy > 0 ||
+               patch->is_new > 0 ||
+               patch->is_delete ||
+               (patch->old_mode && patch->new_mode &&
+                patch->old_mode != patch->new_mode);
+}
+
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -729,6 +891,19 @@ 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)) {
+               static const char binhdr[] = "Binary files ";
+
+               if (sizeof(binhdr) - 1 < size - offset - hdrsize &&
+                   !memcmp(binhdr, buffer + hdrsize, sizeof(binhdr)-1))
+                       patch->is_binary = 1;
+
+               if (patch->is_binary && !apply && !check)
+                       ;
+               else
+                       die("patch with only garbage at line %d", linenr);
+       }
+
        return offset + hdrsize + patchsize;
 }
 
@@ -739,11 +914,18 @@ static void show_stats(struct patch *patch)
 {
        const char *prefix = "";
        char *name = patch->new_name;
+       char *qname = NULL;
        int len, max, add, del, total;
 
        if (!name)
                name = patch->old_name;
 
+       if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+               qname = xmalloc(len + 1);
+               quote_c_style(name, qname, NULL, 0);
+               name = qname;
+       }
+
        /*
         * "scale" the filename
         */
@@ -778,9 +960,14 @@ 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);
 }
 
 static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
@@ -926,8 +1113,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 */
@@ -968,7 +1157,7 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
 
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
-                       return error("patch failed: %s:%d", patch->old_name, frag->oldpos);
+                       return error("patch failed: %s:%ld", patch->old_name, frag->oldpos);
                frag = frag->next;
        }
        return 0;
@@ -1013,17 +1202,39 @@ static int check_patch(struct patch *patch)
 
        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);
+                               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);
                        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));
@@ -1043,8 +1254,12 @@ static int check_patch(struct patch *patch)
                        return error("%s: already exists in working directory", new_name);
                if (errno != ENOENT)
                        return error("%s: %s", new_name, strerror(errno));
-               if (!patch->new_mode)
-                       patch->new_mode = S_IFREG | 0644;
+               if (!patch->new_mode) {
+                       if (patch->is_new)
+                               patch->new_mode = S_IFREG | 0644;
+                       else
+                               patch->new_mode = patch->old_mode;
+               }
        }
 
        if (new_name && old_name) {
@@ -1071,32 +1286,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);
        }
 }
 
@@ -1114,6 +1335,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)
@@ -1199,12 +1434,16 @@ static void patch_stats(struct patch *patch)
        if (lines > max_change)
                max_change = lines;
        if (patch->old_name) {
-               int len = strlen(patch->old_name);
+               int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->old_name);
                if (len > max_len)
                        max_len = len;
        }
        if (patch->new_name) {
-               int len = strlen(patch->new_name);
+               int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->new_name);
                if (len > max_len)
                        max_len = len;
        }
@@ -1373,7 +1612,7 @@ static struct excludes {
 
 static int use_patch(struct patch *p)
 {
-       const char *pathname = p->new_name ? : p->old_name;
+       const char *pathname = p->new_name ? p->new_name : p->old_name;
        struct excludes *x = excludes;
        while (x) {
                if (fnmatch(x->path, pathname, 0) == 0)
@@ -1437,12 +1676,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);
 
@@ -1471,9 +1713,8 @@ 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 (!strcmp(arg, "--no-add")) {
+                       no_add = 1;
                        continue;
                }
                if (!strcmp(arg, "--stat")) {
@@ -1481,6 +1722,11 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--numstat")) {
+                       apply = 0;
+                       numstat = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--summary")) {
                        apply = 0;
                        summary = 1;
@@ -1499,8 +1745,13 @@ 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;
                }
                fd = open(arg, O_RDONLY);