Code

Fix 'git add' with .gitignore
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 2464238ccd6c94a53a0a73de476e33f7cc935ab9..f14288bb8a100c43c6709f658b9a9ca44832fd7f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1,9 +1,6 @@
 /*
  * Copyright (C) 2005 Junio C Hamano
  */
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
 #include "cache.h"
 #include "quote.h"
 #include "diff.h"
@@ -12,6 +9,12 @@
 #include "xdiff-interface.h"
 #include "color.h"
 
+#ifdef NO_FAST_WORKING_DIRECTORY
+#define FAST_WORKING_DIRECTORY 0
+#else
+#define FAST_WORKING_DIRECTORY 1
+#endif
+
 static int use_size_cache;
 
 static int diff_detect_rename_default;
@@ -60,7 +63,7 @@ int git_diff_ui_config(const char *var, const char *value)
                diff_rename_limit_default = git_config_int(var, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
@@ -74,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value)
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
-       if (!strncmp(var, "diff.color.", 11)) {
+       if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
@@ -208,7 +211,7 @@ static void emit_rewrite_diff(const char *name_a,
        diff_populate_filespec(two, 0);
        lc_a = count_lines(one->data, one->size);
        lc_b = count_lines(two->data, two->size);
-       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+       printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
        print_line_count(lc_a);
        printf(" +");
        print_line_count(lc_b);
@@ -635,21 +638,76 @@ static void diffstat_consume(void *priv, char *line, unsigned long len)
                x->deleted++;
 }
 
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
 const char mime_boundary_leader[] = "------------";
 
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+       /*
+        * make sure that at least one '-' is printed if there were deletions,
+        * and likewise for '+'.
+        */
+       if (max_change < 2)
+               return it;
+       return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+                     const char *reset, const char *set)
+{
+       printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+       if (cnt <= 0)
+               return;
+       printf("%s", set);
+       while (cnt--)
+               putchar(ch);
+       printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
 {
        int i, len, add, del, total, adds = 0, dels = 0;
-       int max, max_change = 0, max_len = 0;
+       int max_change = 0, max_len = 0;
        int total_files = data->nr;
+       int width, name_width;
+       const char *reset, *set, *add_c, *del_c;
 
        if (data->nr == 0)
                return;
 
+       width = options->stat_width ? options->stat_width : 80;
+       name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+       /* Sanity: give at least 5 columns to the graph,
+        * but leave at least 10 columns for the name.
+        */
+       if (width < name_width + 15) {
+               if (name_width <= 25)
+                       width = name_width + 15;
+               else
+                       name_width = width - 15;
+       }
+
+       /* Find the longest filename and max number of changes */
+       reset = diff_get_color(options->color_diff, DIFF_RESET);
+       set = diff_get_color(options->color_diff, DIFF_PLAIN);
+       add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+       del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
+               int change = file->added + file->deleted;
+
+               len = quote_c_style(file->name, NULL, NULL, 0);
+               if (len) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(file->name, qname, NULL, 0);
+                       free(file->name);
+                       file->name = qname;
+               }
 
                len = strlen(file->name);
                if (max_len < len)
@@ -657,54 +715,53 @@ static void show_stats(struct diffstat_t* data)
 
                if (file->is_binary || file->is_unmerged)
                        continue;
-               if (max_change < file->added + file->deleted)
-                       max_change = file->added + file->deleted;
+               if (max_change < change)
+                       max_change = change;
        }
 
+       /* Compute the width of the graph part;
+        * 10 is for one blank at the beginning of the line plus
+        * " | count " between the name and the graph.
+        *
+        * From here on, name_width is the width of the name area,
+        * and width is the width of the graph area.
+        */
+       name_width = (name_width < max_len) ? name_width : max_len;
+       if (width < (name_width + 10) + max_change)
+               width = width - (name_width + 10);
+       else
+               width = max_change;
+
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->name;
                int added = data->files[i]->added;
                int deleted = data->files[i]->deleted;
-
-               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
-                       char *qname = xmalloc(len + 1);
-                       quote_c_style(name, qname, NULL, 0);
-                       free(name);
-                       data->files[i]->name = name = qname;
-               }
+               int name_len;
 
                /*
                 * "scale" the filename
                 */
-               len = strlen(name);
-               max = max_len;
-               if (max > 50)
-                       max = 50;
-               if (len > max) {
+               len = name_width;
+               name_len = strlen(name);
+               if (name_width < name_len) {
                        char *slash;
                        prefix = "...";
-                       max -= 3;
-                       name += len - max;
+                       len -= 3;
+                       name += name_len - len;
                        slash = strchr(name, '/');
                        if (slash)
                                name = slash;
                }
-               len = max;
-
-               /*
-                * scale the add/delete
-                */
-               max = max_change;
-               if (max + len > 70)
-                       max = 70 - len;
 
                if (data->files[i]->is_binary) {
-                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Bin\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
-                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Unmerged\n");
                        goto free_diffstat_file;
                }
                else if (!data->files[i]->is_renamed &&
@@ -713,27 +770,81 @@ static void show_stats(struct diffstat_t* data)
                        goto free_diffstat_file;
                }
 
+               /*
+                * scale the add/delete
+                */
                add = added;
                del = deleted;
                total = add + del;
                adds += add;
                dels += del;
 
-               if (max_change > 0) {
-                       total = (total * max + max_change / 2) / max_change;
-                       add = (add * max + max_change / 2) / max_change;
-                       del = total - add;
+               if (width <= max_change) {
+                       add = scale_linear(add, width, max_change);
+                       del = scale_linear(del, width, max_change);
+                       total = add + del;
                }
-               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
-                               len, name, added + deleted,
-                               add, pluses, del, minuses);
+               show_name(prefix, name, len, reset, set);
+               printf("%5d ", added + deleted);
+               show_graph('+', add, add_c, reset);
+               show_graph('-', del, del_c, reset);
+               putchar('\n');
        free_diffstat_file:
                free(data->files[i]->name);
                free(data->files[i]);
        }
        free(data->files);
+       printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+              set, total_files, adds, dels, reset);
+}
+
+static void show_shortstats(struct diffstat_t* data)
+{
+       int i, adds = 0, dels = 0, total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               if (!data->files[i]->is_binary &&
+                   !data->files[i]->is_unmerged) {
+                       int added = data->files[i]->added;
+                       int deleted= data->files[i]->deleted;
+                       if (!data->files[i]->is_renamed &&
+                           (added + deleted == 0)) {
+                               total_files--;
+                       } else {
+                               adds += added;
+                               dels += deleted;
+                       }
+               }
+               free(data->files[i]->name);
+               free(data->files[i]);
+       }
+       free(data->files);
+
        printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
-                       total_files, adds, dels);
+              total_files, adds, dels);
+}
+
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+       int i;
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+
+               if (file->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t", file->added, file->deleted);
+               if (options->line_termination &&
+                   quote_c_style(file->name, NULL, NULL, 0))
+                       quote_c_style(file->name, NULL, stdout, 0);
+               else
+                       fputs(file->name, stdout);
+               putchar(options->line_termination);
+       }
 }
 
 struct checkdiff_t {
@@ -749,8 +860,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        if (line[0] == '+') {
                int i, spaces = 0;
 
-               data->lineno++;
-
                /* check space before tab */
                for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
                        if (line[i] == ' ')
@@ -765,6 +874,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
                if (isspace(line[len - 1]))
                        printf("%s:%d: white space at end: %.*s\n",
                                data->filename, data->lineno, (int)len, line);
+
+               data->lineno++;
        } else if (line[0] == ' ')
                data->lineno++;
        else if (line[0] == '@') {
@@ -1079,7 +1190,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
  * the work tree has that object contents, return true, so that
  * prepare_temp_file() does not have to inflate and extract.
  */
-static int work_tree_matches(const char *name, const unsigned char *sha1)
+static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
 {
        struct cache_entry *ce;
        struct stat st;
@@ -1100,6 +1211,18 @@ static int work_tree_matches(const char *name, const unsigned char *sha1)
        if (!active_cache)
                return 0;
 
+       /* We want to avoid the working directory if our caller
+        * doesn't need the data in a normal file, this system
+        * is rather slow with its stat/open/mmap/close syscalls,
+        * and the object is contained in a pack file.  The pack
+        * is probably already open and will be faster to obtain
+        * the data through than the working directory.  Loose
+        * objects however would tend to be slower as they need
+        * to be individually opened and inflated.
+        */
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1, NULL))
+               return 0;
+
        len = strlen(name);
        pos = cache_name_pos(name, len);
        if (pos < 0)
@@ -1186,7 +1309,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (s->data)
                return err;
        if (!s->sha1_valid ||
-           work_tree_matches(s->path, s->sha1)) {
+           reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                int fd;
                if (lstat(s->path, &st) < 0) {
@@ -1293,7 +1416,7 @@ static void prepare_temp_file(const char *name,
        }
 
        if (!one->sha1_valid ||
-           work_tree_matches(name, one->sha1)) {
+           reuse_worktree_file(name, one->sha1, 1)) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
@@ -1672,7 +1795,9 @@ int diff_setup_done(struct diff_options *options)
                                      DIFF_FORMAT_CHECKDIFF |
                                      DIFF_FORMAT_NO_OUTPUT))
                options->output_format &= ~(DIFF_FORMAT_RAW |
+                                           DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SHORTSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
 
@@ -1681,7 +1806,10 @@ int diff_setup_done(struct diff_options *options)
         * recursive bits for other formats here.
         */
        if (options->output_format & (DIFF_FORMAT_PATCH |
+                                     DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_SHORTSTAT |
+                                     DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
        /*
@@ -1769,8 +1897,39 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
-       else if (!strcmp(arg, "--stat"))
+       else if (!strcmp(arg, "--numstat")) {
+               options->output_format |= DIFF_FORMAT_NUMSTAT;
+       }
+       else if (!strcmp(arg, "--shortstat")) {
+               options->output_format |= DIFF_FORMAT_SHORTSTAT;
+       }
+       else if (!strncmp(arg, "--stat", 6)) {
+               char *end;
+               int width = options->stat_width;
+               int name_width = options->stat_name_width;
+               arg += 6;
+               end = (char *)arg;
+
+               switch (*arg) {
+               case '-':
+                       if (!strncmp(arg, "-width=", 7))
+                               width = strtoul(arg + 7, &end, 10);
+                       else if (!strncmp(arg, "-name-width=", 12))
+                               name_width = strtoul(arg + 12, &end, 10);
+                       break;
+               case '=':
+                       width = strtoul(arg+1, &end, 10);
+                       if (*end == ',')
+                               name_width = strtoul(end+1, &end, 10);
+               }
+
+               /* Important! This checks all the error cases! */
+               if (*end)
+                       return 0;
                options->output_format |= DIFF_FORMAT_DIFFSTAT;
+               options->stat_name_width = name_width;
+               options->stat_width = width;
+       }
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@ -2518,7 +2677,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & DIFF_FORMAT_DIFFSTAT) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2528,7 +2687,12 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(&diffstat);
+               if (output_format & DIFF_FORMAT_NUMSTAT)
+                       show_numstat(&diffstat, options);
+               if (output_format & DIFF_FORMAT_DIFFSTAT)
+                       show_stats(&diffstat, options);
+               else if (output_format & DIFF_FORMAT_SHORTSTAT)
+                       show_shortstats(&diffstat);
                separator++;
        }