Fix git-fetch-script breakage
[gitweb.git] / apply.c
diff --git a/apply.c b/apply.c
index cf55fda71edba2ea4a78d52e714e37f46fda9691..a3474b788bca2b1dbbeba4c417a5489b4ae1c273 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -28,10 +28,12 @@ static int merge_patch = 1;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
+static int summary = 0;
 static int check = 0;
 static int apply = 1;
 static int show_files = 0;
-static const char apply_usage[] = "git-apply [--stat] [--check] [--show-files] <patch>";
+static const char apply_usage[] =
+"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -60,6 +62,7 @@ struct patch {
        unsigned int old_mode, new_mode;
        int is_rename, is_copy, is_new, is_delete;
        int lines_added, lines_deleted;
+       int score;
        struct fragment *fragments;
        char *result;
        unsigned long resultsize;
@@ -329,11 +332,15 @@ static int gitdiff_renamedst(const char *line, struct patch *patch)
 
 static int gitdiff_similarity(const char *line, struct patch *patch)
 {
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
        return 0;
 }
 
 static int gitdiff_dissimilarity(const char *line, struct patch *patch)
 {
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
        return 0;
 }
 
@@ -435,6 +442,8 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                        { "new file mode ", gitdiff_newfile },
                        { "copy from ", gitdiff_copysrc },
                        { "copy to ", gitdiff_copydst },
+                       { "rename old ", gitdiff_renamesrc },
+                       { "rename new ", gitdiff_renamedst },
                        { "rename from ", gitdiff_renamesrc },
                        { "rename to ", gitdiff_renamedst },
                        { "similarity index ", gitdiff_similarity },
@@ -566,10 +575,13 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                 */
                if (!memcmp("diff --git ", line, 11)) {
                        int git_hdr_len = parse_git_header(line, len, size, patch);
-                       if (git_hdr_len < 0)
+                       if (git_hdr_len <= len)
                                continue;
-                       if (!patch->old_name && !patch->new_name)
-                               die("git diff header lacks filename information");
+                       if (!patch->old_name && !patch->new_name) {
+                               if (!patch->def_name)
+                                       die("git diff header lacks filename information (line %d)", linenr);
+                               patch->old_name = patch->new_name = patch->def_name;
+                       }
                        *hdrsize = git_hdr_len;
                        return offset;
                }
@@ -628,8 +640,11 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
 
        if (patch->is_new != !oldlines)
                return error("new file depends on old contents");
-       if (patch->is_delete != !newlines)
-               return error("deleted file still has contents");
+       if (patch->is_delete != !newlines) {
+               if (newlines)
+                       return error("deleted file still has contents");
+               fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
+       }
 
        /* Parse the thing.. */
        line += len;
@@ -710,16 +725,16 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
        return offset + hdrsize + patchsize;
 }
 
-const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-const char minuses[]= "----------------------------------------------------------------------";
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
 
 static void show_stats(struct patch *patch)
 {
-       char *name = patch->old_name;
+       char *name = patch->new_name;
        int len, max, add, del, total;
 
        if (!name)
-               name = patch->new_name;
+               name = patch->old_name;
 
        /*
         * "scale" the filename
@@ -743,9 +758,11 @@ static void show_stats(struct patch *patch)
        del = patch->lines_deleted;
        total = add + del;
 
-       total = (total * max + max_change / 2) / max_change;
-       add = (add * max + max_change / 2) / max_change;
-       del = total - add;
+       if (max_change > 0) {
+               total = (total * max + max_change / 2) / max_change;
+               add = (add * max + max_change / 2) / max_change;
+               del = total - add;
+       }
        printf(" %-*s |%5d %.*s%.*s\n",
                len, name, patch->lines_added + patch->lines_deleted,
                add, pluses, del, minuses);
@@ -844,7 +861,6 @@ static int find_offset(const char *buf, unsigned long size, const char *fragment
                n = (i >> 1)+1;
                if (i & 1)
                        n = -n;
-               fprintf(stderr, "Fragment applied at offset %d\n", n);
                return try;
        }
 
@@ -984,7 +1000,7 @@ static int check_patch(struct patch *patch)
                int changed;
 
                if (lstat(old_name, &st) < 0)
-                       return error("%s: %s\n", strerror(errno));
+                       return error("%s: %s", old_name, strerror(errno));
                if (check_index) {
                        int pos = cache_name_pos(old_name, strlen(old_name));
                        if (pos < 0)
@@ -995,6 +1011,7 @@ static int check_patch(struct patch *patch)
                }
                if (patch->is_new < 0)
                        patch->is_new = 0;
+               st.st_mode = ntohl(create_ce_mode(st.st_mode));
                if (!patch->old_mode)
                        patch->old_mode = st.st_mode;
                if ((st.st_mode ^ patch->old_mode) & S_IFMT)
@@ -1082,6 +1099,84 @@ static void stat_patch_list(struct patch *patch)
        printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 }
 
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+       if (mode)
+               printf(" %s mode %06o %s\n", newdelete, mode, name);
+       else
+               printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+       if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->old_mode, p->new_mode, p->new_name);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->old_mode, p->new_mode);
+       }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+       const char *renamecopy = p->is_rename ? "rename" : "copy";
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->old_name;
+       new = p->new_name;
+       while (1) {
+               const char *slash_old, *slash_new;
+               slash_old = strchr(old, '/');
+               slash_new = strchr(new, '/');
+               if (!slash_old ||
+                   !slash_new ||
+                   slash_old - old != slash_new - new ||
+                   memcmp(old, new, slash_new - new))
+                       break;
+               old = slash_old + 1;
+               new = slash_new + 1;
+       }
+       /* p->old_name thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->old_name)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->old_name), p->old_name,
+                      old, new, p->score);
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->old_name, p->new_name, p->score);
+       show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+       struct patch *p;
+
+       for (p = patch; p; p = p->next) {
+               if (p->is_new)
+                       show_file_mode_name("create", p->new_mode, p->new_name);
+               else if (p->is_delete)
+                       show_file_mode_name("delete", p->old_mode, p->old_name);
+               else {
+                       if (p->is_rename || p->is_copy)
+                               show_rename_copy(p);
+                       else {
+                               if (p->score) {
+                                       printf(" rewrite %s (%d%%)\n",
+                                              p->new_name, p->score);
+                                       show_mode_change(p, 0);
+                               }
+                               else
+                                       show_mode_change(p, 1);
+                       }
+               }
+       }
+}
+
 static void patch_stats(struct patch *patch)
 {
        int lines = patch->lines_added + patch->lines_deleted;
@@ -1133,6 +1228,85 @@ 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);
+       if (fd < 0)
+               return -1;
+       while (size) {
+               int written = write(fd, buf, size);
+               if (written < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       die("writing file %s: %s", path, strerror(errno));
+               }
+               if (!written)
+                       die("out of space writing file %s", path);
+               buf += written;
+               size -= written;
+       }
+       if (close(fd) < 0)
+               die("closing file %s: %s", path, strerror(errno));
+       return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * 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)
+{
+       if (!try_create_file(path, mode, buf, size))
+               return;
+
+       if (errno == ENOENT) {
+               create_subdirectories(path);
+               if (!try_create_file(path, mode, buf, size))
+                       return;
+       }
+
+       if (errno == EEXIST) {
+               unsigned int nr = getpid();
+
+               for (;;) {
+                       const char *newpath;
+                       newpath = mkpath("%s~%u", path, nr);
+                       if (!try_create_file(newpath, mode, buf, size)) {
+                               if (!rename(newpath, path))
+                                       return;
+                               unlink(newpath);
+                               break;
+                       }
+                       if (errno != EEXIST)
+                               break;
+               }                       
+       }
+       die("unable to write file %s mode %o", path, mode);
+}
+
 static void create_file(struct patch *patch)
 {
        const char *path = patch->new_name;
@@ -1142,28 +1316,8 @@ static void create_file(struct patch *patch)
 
        if (!mode)
                mode = S_IFREG | 0644;
-       if (S_ISREG(mode)) {
-               int fd;
-               mode = (mode & 0100) ? 0777 : 0666;
-               fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
-               if (fd < 0)
-                       die("unable to create file %s (%s)", path, strerror(errno));
-               if (write(fd, buf, size) != size)
-                       die("unable to write file %s", path);
-               close(fd);
-               add_index_file(path, mode, buf, size);
-               return;
-       }
-       if (S_ISLNK(mode)) {
-               if (size && buf[size-1] == '\n')
-                       size--;
-               buf[size] = 0;
-               if (symlink(buf, path) < 0)
-                       die("unable to write symlink %s", path);
-               add_index_file(path, mode, buf, size);
-               return;
-       }
-       die("unable to write file mode %o", mode);
+       create_one_file(path, mode, buf, size); 
+       add_index_file(path, mode, buf, size);
 }
 
 static void write_out_one_result(struct patch *patch)
@@ -1186,6 +1340,9 @@ static void write_out_one_result(struct patch *patch)
 
 static void write_out_results(struct patch *list)
 {
+       if (!list)
+               die("No changes");
+
        while (list) {
                write_out_one_result(list);
                list = list->next;
@@ -1247,6 +1404,9 @@ static int apply_patch(int fd)
        if (diffstat)
                stat_patch_list(list);
 
+       if (summary)
+               summary_patch_list(list);
+
        free(buffer);
        return 0;
 }
@@ -1265,6 +1425,7 @@ int main(int argc, char **argv)
                        read_stdin = 0;
                        continue;
                }
+               /* NEEDSWORK: this does not do anything at this moment. */
                if (!strcmp(arg, "--no-merge")) {
                        merge_patch = 0;
                        continue;
@@ -1274,6 +1435,11 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--summary")) {
+                       apply = 0;
+                       summary = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--check")) {
                        apply = 0;
                        check = 1;
@@ -1283,6 +1449,10 @@ int main(int argc, char **argv)
                        check_index = 1;
                        continue;
                }
+               if (!strcmp(arg, "--apply")) {
+                       apply = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--show-files")) {
                        show_files = 1;
                        continue;