pretty_print_commit: add different formats
[git.git] / apply.c
diff --git a/apply.c b/apply.c
index 3606419..e0a1549 100644 (file)
--- a/apply.c
+++ b/apply.c
 #include "cache.h"
 
 // We default to the merge behaviour, since that's what most people would
-// expect
+// 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
+//
 static int merge_patch = 1;
 static int diffstat = 0;
-static int check = 1;
-static const char apply_usage[] = "git-apply <patch>";
+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>";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -108,9 +116,8 @@ static int is_dev_null(const char *str)
        return !memcmp("/dev/null", str, 9) && isspace(str[9]);
 }
 
-#define TERM_EXIST     1
-#define TERM_SPACE     2
-#define TERM_TAB       4
+#define TERM_SPACE     1
+#define TERM_TAB       2
 
 static int name_terminate(const char *name, int namelen, int c, int terminate)
 {
@@ -119,13 +126,6 @@ static int name_terminate(const char *name, int namelen, int c, int terminate)
        if (c == '\t' && !(terminate & TERM_TAB))
                return 0;
 
-       /*
-        * Do we want an existing name? Return false and
-        * continue if it's not there.
-        */
-       if (terminate & TERM_EXIST)
-               return cache_name_pos(name, namelen) >= 0;
-
        return 1;
 }
 
@@ -197,11 +197,11 @@ static void parse_traditional_patch(const char *first, const char *second, struc
        } else if (is_dev_null(second)) {
                patch->is_new = 0;
                patch->is_delete = 1;
-               name = find_name(first, NULL, p_value, TERM_EXIST | TERM_SPACE | TERM_TAB);
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
                patch->old_name = name;
        } else {
-               name = find_name(first, NULL, p_value, TERM_EXIST | TERM_SPACE | TERM_TAB);
-               name = find_name(second, name, p_value, TERM_EXIST | TERM_SPACE | TERM_TAB);
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+               name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
                patch->old_name = patch->new_name = name;
        }
        if (!name)
@@ -328,6 +328,11 @@ static int gitdiff_similarity(const char *line, struct patch *patch)
        return 0;
 }
 
+static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+{
+       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.
@@ -381,7 +386,7 @@ static char *git_header_name(char *line)
                                if (c == '/')
                                        break;
                        }
-                       if (!memcmp(name, second, len)) {
+                       if (second[len] == '\n' && !memcmp(name, second, len)) {
                                char *ret = xmalloc(len + 1);
                                memcpy(ret, name, len);
                                ret[len] = 0;
@@ -429,6 +434,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                        { "rename from ", gitdiff_renamesrc },
                        { "rename to ", gitdiff_renamedst },
                        { "similarity index ", gitdiff_similarity },
+                       { "dissimilarity index ", gitdiff_dissimilarity },
                        { "", gitdiff_unrecognized },
                };
                int i;
@@ -693,7 +699,7 @@ const char minuses[]= "---------------------------------------------------------
 static void show_stats(struct patch *patch)
 {
        char *name = patch->old_name;
-       int len, max, add, del;
+       int len, max, add, del, total;
 
        if (!name)
                name = patch->new_name;
@@ -715,52 +721,103 @@ static void show_stats(struct patch *patch)
        max = max_change;
        if (max + len > 70)
                max = 70 - len;
-       
-       add = (patch->lines_added * max + max_change/2) / max_change;
-       del = (patch->lines_deleted * max + max_change/2) / max_change;
+
+       add = patch->lines_added;
+       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;
        printf(" %-*s |%5d %.*s%.*s\n",
                len, name, patch->lines_added + patch->lines_deleted,
                add, pluses, del, minuses);
 }
 
-static void check_patch(struct patch *patch)
+static int check_patch(struct patch *patch)
 {
+       struct stat st;
        const char *old_name = patch->old_name;
        const char *new_name = patch->new_name;
 
        if (old_name) {
-               if (cache_name_pos(old_name, strlen(old_name)) < 0)
-                       die("file %s does not exist", old_name);
+               int pos = cache_name_pos(old_name, strlen(old_name));
+               int changed;
+
+               if (pos < 0)
+                       return error("%s: does not exist in index", old_name);
                if (patch->is_new < 0)
                        patch->is_new = 0;
+               if (lstat(old_name, &st) < 0)
+                       return error("%s: %s\n", strerror(errno));
+               changed = ce_match_stat(active_cache[pos], &st);
+               if (changed)
+                       return error("%s: does not match index", old_name);
+               if (!patch->old_mode)
+                       patch->old_mode = st.st_mode;
        }
+
        if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
                if (cache_name_pos(new_name, strlen(new_name)) >= 0)
-                       die("file %s already exists", new_name);
+                       return error("%s: already exists in index", new_name);
+               if (!lstat(new_name, &st))
+                       return error("%s: already exists in working directory", new_name);
+               if (errno != ENOENT)
+                       return error("%s: %s", new_name, strerror(errno));
        }
+       return 0;
 }
 
-static void apply_patch_list(struct patch *patch)
+static int check_patch_list(struct patch *patch)
 {
-       int files, adds, dels;
+       int error = 0;
+
+       for (;patch ; patch = patch->next)
+               error |= check_patch(patch);
+       return error;
+}
+
+static void show_file(int c, unsigned int mode, const char *name)
+{
+       printf("%c %o %s\n", c, mode, name);
+}
 
-       files = adds = dels = 0;
-       if (!patch)
-               die("no patch found");
-       do {
-               if (check)
-                       check_patch(patch);
-
-               if (diffstat) {
-                       files++;
-                       adds += patch->lines_added;
-                       dels += patch->lines_deleted;
-                       show_stats(patch);
+static void show_file_list(struct patch *patch)
+{
+       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;
                }
-       } while ((patch = patch->next) != NULL);
+               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);
+       }
+}
 
-       if (diffstat)
-               printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+static void stat_patch_list(struct patch *patch)
+{
+       int files, adds, dels;
+
+       for (files = adds = dels = 0 ; patch ; patch = patch->next) {
+               files++;
+               adds += patch->lines_added;
+               dels += patch->lines_deleted;
+               show_stats(patch);
+       }
+
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 }
 
 static void patch_stats(struct patch *patch)
@@ -806,7 +863,14 @@ static int apply_patch(int fd)
                size -= nr;
        }
 
-       apply_patch_list(list);
+       if ((check || apply) && check_patch_list(list) < 0)
+               exit(1);
+
+       if (show_files)
+               show_file_list(list);
+
+       if (diffstat)
+               stat_patch_list(list);
 
        free(buffer);
        return 0;
@@ -834,10 +898,19 @@ int main(int argc, char **argv)
                        continue;
                }
                if (!strcmp(arg, "--stat")) {
-                       check = 0;
+                       apply = 0;
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--check")) {
+                       apply = 0;
+                       check = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--show-files")) {
+                       show_files = 1;
+                       continue;
+               }
                fd = open(arg, O_RDONLY);
                if (fd < 0)
                        usage(apply_usage);