summaryrefslogtreecommitdiff
path: root/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'diff.c')
-rw-r--r--diff.c687
1 files changed, 536 insertions, 151 deletions
diff --git a/diff.c b/diff.c
index 5422c43..377ec1e 100644
--- a/diff.c
+++ b/diff.c
@@ -23,7 +23,7 @@
#endif
static int diff_detect_rename_default;
-static int diff_rename_limit_default = 200;
+static int diff_rename_limit_default = 400;
static int diff_suppress_blank_empty;
int diff_use_color_default = -1;
static const char *diff_word_regex_cfg;
@@ -31,6 +31,8 @@ static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
static int diff_no_prefix;
+static int diff_stat_graph_width;
+static int diff_dirstat_permille_default = 30;
static struct diff_options default_diff_options;
static char diff_colors[][COLOR_MAXLEN] = {
@@ -66,6 +68,58 @@ static int parse_diff_color_slot(const char *var, int ofs)
return -1;
}
+static int parse_dirstat_params(struct diff_options *options, const char *params,
+ struct strbuf *errmsg)
+{
+ const char *p = params;
+ int p_len, ret = 0;
+
+ while (*p) {
+ p_len = strchrnul(p, ',') - p;
+ if (!memcmp(p, "changes", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "lines", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "files", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "noncumulative", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+ } else if (!memcmp(p, "cumulative", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+ } else if (isdigit(*p)) {
+ char *end;
+ int permille = strtoul(p, &end, 10) * 10;
+ if (*end == '.' && isdigit(*++end)) {
+ /* only use first digit */
+ permille += *end - '0';
+ /* .. and ignore any further digits */
+ while (isdigit(*++end))
+ ; /* nothing */
+ }
+ if (end - p == p_len)
+ options->dirstat_permille = permille;
+ else {
+ strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+ } else {
+ strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+
+ p += p_len;
+
+ if (*p)
+ p++; /* more parameters, swallow separator */
+ }
+ return ret;
+}
+
static int git_config_rename(const char *var, const char *value)
{
if (!value)
@@ -84,7 +138,7 @@ static int git_config_rename(const char *var, const char *value)
int git_diff_ui_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
- diff_use_color_default = git_config_colorbool(var, value, -1);
+ diff_use_color_default = git_config_colorbool(var, value);
return 0;
}
if (!strcmp(var, "diff.renames")) {
@@ -103,6 +157,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
diff_no_prefix = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.statgraphwidth")) {
+ diff_stat_graph_width = git_config_int(var, value);
+ return 0;
+ }
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
if (!strcmp(var, "diff.wordregex"))
@@ -111,6 +169,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "diff.ignoresubmodules"))
handle_ignore_submodules_arg(&default_diff_options, value);
+ if (git_color_config(var, value, cb) < 0)
+ return -1;
+
return git_diff_basic_config(var, value, cb);
}
@@ -121,11 +182,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
+ if (userdiff_config(var, value) < 0)
+ return -1;
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
@@ -145,10 +203,21 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp(var, "diff.dirstat")) {
+ struct strbuf errmsg = STRBUF_INIT;
+ default_diff_options.dirstat_permille = diff_dirstat_permille_default;
+ if (parse_dirstat_params(&default_diff_options, value, &errmsg))
+ warning(_("Found errors in 'diff.dirstat' config variable:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ diff_dirstat_permille_default = default_diff_options.dirstat_permille;
+ return 0;
+ }
+
if (!prefixcmp(var, "submodule."))
return parse_submodule_config_option(var, value);
- return git_color_default_config(var, value, cb);
+ return git_default_config(var, value, cb);
}
static char *quote_two(const char *one, const char *two)
@@ -245,6 +314,15 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
return 0;
}
+/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
+static unsigned long diff_filespec_size(struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one))
+ return 0;
+ diff_populate_filespec(one, 1);
+ return one->size;
+}
+
static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
{
char *ptr = mf->ptr;
@@ -510,11 +588,10 @@ static void emit_rewrite_diff(const char *name_a,
struct diff_options *o)
{
int lc_a, lc_b;
- int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
const char *name_a_tab, *name_b_tab;
- const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
- const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
- const char *reset = diff_get_color(color_diff, DIFF_RESET);
+ const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+ const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+ const char *reset = diff_get_color(o->use_color, DIFF_RESET);
static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
const char *a_prefix, *b_prefix;
char *data_one, *data_two;
@@ -550,7 +627,7 @@ static void emit_rewrite_diff(const char *name_a,
size_two = fill_textconv(textconv_two, two, &data_two);
memset(&ecbdata, 0, sizeof(ecbdata));
- ecbdata.color_diff = color_diff;
+ ecbdata.color_diff = want_color(o->use_color);
ecbdata.found_changesp = &o->found_changes;
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
ecbdata.opt = o;
@@ -572,11 +649,14 @@ static void emit_rewrite_diff(const char *name_a,
line_prefix, metainfo, a_name.buf, name_a_tab, reset,
line_prefix, metainfo, b_name.buf, name_b_tab, reset,
line_prefix, fraginfo);
- print_line_count(o->file, lc_a);
+ if (!o->irreversible_delete)
+ print_line_count(o->file, lc_a);
+ else
+ fprintf(o->file, "?,?");
fprintf(o->file, " +");
print_line_count(o->file, lc_b);
fprintf(o->file, " @@%s\n", reset);
- if (lc_a)
+ if (lc_a && !o->irreversible_delete)
emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
if (lc_b)
emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
@@ -606,22 +686,20 @@ static void diff_words_append(char *line, unsigned long len,
buffer->text.ptr[buffer->text.size] = '\0';
}
-struct diff_words_style_elem
-{
+struct diff_words_style_elem {
const char *prefix;
const char *suffix;
const char *color; /* NULL; filled in by the setup code if
* color is enabled */
};
-struct diff_words_style
-{
+struct diff_words_style {
enum diff_words_type type;
struct diff_words_style_elem new, old, ctx;
const char *newline;
};
-struct diff_words_style diff_words_styles[] = {
+static struct diff_words_style diff_words_styles[] = {
{ DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
{ DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
{ DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
@@ -930,7 +1008,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
const char *diff_get_color(int diff_use_color, enum color_diff ix)
{
- if (diff_use_color)
+ if (want_color(diff_use_color))
return diff_colors[ix];
return "";
}
@@ -1037,14 +1115,31 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
diff_words_append(line, len,
&ecbdata->diff_words->plus);
return;
+ } else if (!prefixcmp(line, "\\ ")) {
+ /*
+ * Eat the "no newline at eof" marker as if we
+ * saw a "+" or "-" line with nothing on it,
+ * and return without diff_words_flush() to
+ * defer processing. If this is the end of
+ * preimage, more "+" lines may come after it.
+ */
+ return;
}
diff_words_flush(ecbdata);
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
emit_line(ecbdata->opt, plain, reset, line, len);
fputs("~\n", ecbdata->opt->file);
} else {
- /* don't print the prefix character */
- emit_line(ecbdata->opt, plain, reset, line+1, len-1);
+ /*
+ * Skip the prefix character, if any. With
+ * diff_suppress_blank_empty, there may be
+ * none.
+ */
+ if (line[0] != '\n') {
+ line++;
+ len--;
+ }
+ emit_line(ecbdata->opt, plain, reset, line, len);
}
return;
}
@@ -1183,13 +1278,15 @@ const char mime_boundary_leader[] = "------------";
static int scale_linear(int it, int width, int max_change)
{
+ if (!it)
+ return 0;
/*
- * make sure that at least one '-' is printed if there were deletions,
- * and likewise for '+'.
+ * make sure that at least one '-' or '+' is printed if
+ * there is any change to this path. The easiest way is to
+ * scale linearly as if the alloted width is one column shorter
+ * than it is, and then add 1 to the result.
*/
- if (max_change < 2)
- return it;
- return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+ return 1 + (it * (width - 1) / max_change);
}
static void show_name(FILE *file,
@@ -1229,14 +1326,64 @@ static void fill_print_name(struct diffstat_file *file)
file->print_name = pname;
}
+int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!files) {
+ assert(insertions == 0 && deletions == 0);
+ return fputs(_(" 0 files changed\n"), fp);
+ }
+
+ strbuf_addf(&sb,
+ Q_(" %d file changed", " %d files changed", files),
+ files);
+
+ /*
+ * For binary diff, the caller may want to print "x files
+ * changed" with insertions == 0 && deletions == 0.
+ *
+ * Not omitting "0 insertions(+), 0 deletions(-)" in this case
+ * is probably less confusing (i.e skip over "2 files changed
+ * but nothing about added/removed lines? Is this a bug in Git?").
+ */
+ if (insertions || deletions == 0) {
+ /*
+ * TRANSLATORS: "+" in (+) is a line addition marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d insertion(+)", ", %d insertions(+)",
+ insertions),
+ insertions);
+ }
+
+ if (deletions || insertions == 0) {
+ /*
+ * TRANSLATORS: "-" in (-) is a line removal marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d deletion(-)", ", %d deletions(-)",
+ deletions),
+ deletions);
+ }
+ strbuf_addch(&sb, '\n');
+ ret = fputs(sb.buf, fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
static void show_stats(struct diffstat_t *data, struct diff_options *options)
{
int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0;
int total_files = data->nr;
- int width, name_width;
- const char *reset, *set, *add_c, *del_c;
+ int width, name_width, graph_width, number_width = 4, count;
+ const char *reset, *add_c, *del_c;
const char *line_prefix = "";
+ int extra_shown = 0;
struct strbuf *msg = NULL;
if (data->nr == 0)
@@ -1247,28 +1394,23 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
line_prefix = msg->buf;
}
- width = options->stat_width ? options->stat_width : 80;
- name_width = options->stat_name_width ? options->stat_name_width : 50;
+ count = options->stat_count ? options->stat_count : data->nr;
- /* Sanity: give at least 5 columns to the graph,
- * but leave at least 10 columns for the name.
- */
- if (width < 25)
- width = 25;
- if (name_width < 10)
- name_width = 10;
- else if (width < name_width + 15)
- name_width = width - 15;
-
- /* Find the longest filename and max number of changes */
reset = diff_get_color_opt(options, DIFF_RESET);
- set = diff_get_color_opt(options, DIFF_PLAIN);
add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
- for (i = 0; i < data->nr; i++) {
+ /*
+ * Find the longest filename and max number of changes
+ */
+ for (i = 0; (i < count) && (i < data->nr); i++) {
struct diffstat_file *file = data->files[i];
uintmax_t change = file->added + file->deleted;
+ if (!data->files[i]->is_renamed &&
+ (change == 0)) {
+ count++; /* not shown == room for one more */
+ continue;
+ }
fill_print_name(file);
len = strlen(file->print_name);
if (max_len < len)
@@ -1279,27 +1421,86 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
if (max_change < change)
max_change = change;
}
+ count = i; /* min(count, data->nr) */
- /* 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.
+ /*
+ * We have width = stat_width or term_columns() columns total.
+ * We want a maximum of min(max_len, stat_name_width) for the name part.
+ * We want a maximum of min(max_change, stat_graph_width) for the +- part.
+ * We also need 1 for " " and 4 + decimal_width(max_change)
+ * for " | NNNN " and one the empty column at the end, altogether
+ * 6 + decimal_width(max_change).
+ *
+ * If there's not enough space, we will use the smaller of
+ * stat_name_width (if set) and 5/8*width for the filename,
+ * and the rest for constant elements + graph part, but no more
+ * than stat_graph_width for the graph part.
+ * (5/8 gives 50 for filename and 30 for the constant parts + graph
+ * for the standard terminal size).
*
- * From here on, name_width is the width of the name area,
- * and width is the width of the graph area.
+ * In other words: stat_width limits the maximum width, and
+ * stat_name_width fixes the maximum width of the filename,
+ * and is also used to divide available columns if there
+ * aren't enough.
*/
- name_width = (name_width < max_len) ? name_width : max_len;
- if (width < (name_width + 10) + max_change)
- width = width - (name_width + 10);
+
+ if (options->stat_width == -1)
+ width = term_columns();
else
- width = max_change;
+ width = options->stat_width ? options->stat_width : 80;
- for (i = 0; i < data->nr; i++) {
+ if (options->stat_graph_width == -1)
+ options->stat_graph_width = diff_stat_graph_width;
+
+ /*
+ * Guarantee 3/8*16==6 for the graph part
+ * and 5/8*16==10 for the filename part
+ */
+ if (width < 16 + 6 + number_width)
+ width = 16 + 6 + number_width;
+
+ /*
+ * First assign sizes that are wanted, ignoring available width.
+ */
+ graph_width = (options->stat_graph_width &&
+ options->stat_graph_width < max_change) ?
+ options->stat_graph_width : max_change;
+ name_width = (options->stat_name_width > 0 &&
+ options->stat_name_width < max_len) ?
+ options->stat_name_width : max_len;
+
+ /*
+ * Adjust adjustable widths not to exceed maximum width
+ */
+ if (name_width + number_width + 6 + graph_width > width) {
+ if (graph_width > width * 3/8 - number_width - 6)
+ graph_width = width * 3/8 - number_width - 6;
+ if (options->stat_graph_width &&
+ graph_width > options->stat_graph_width)
+ graph_width = options->stat_graph_width;
+ if (name_width > width - number_width - 6 - graph_width)
+ name_width = width - number_width - 6 - graph_width;
+ else
+ graph_width = width - number_width - 6 - name_width;
+ }
+
+ /*
+ * From here name_width is the width of the name area,
+ * and graph_width is the width of the graph area.
+ * max_change is used to scale graph properly.
+ */
+ for (i = 0; i < count; i++) {
const char *prefix = "";
char *name = data->files[i]->print_name;
uintmax_t added = data->files[i]->added;
uintmax_t deleted = data->files[i]->deleted;
int name_len;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
/*
* "scale" the filename
*/
@@ -1334,11 +1535,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
fprintf(options->file, " Unmerged\n");
continue;
}
- else if (!data->files[i]->is_renamed &&
- (added + deleted == 0)) {
- total_files--;
- continue;
- }
/*
* scale the add/delete
@@ -1348,9 +1544,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
adds += add;
dels += del;
- if (width <= max_change) {
- add = scale_linear(add, width, max_change);
- del = scale_linear(del, width, max_change);
+ if (graph_width <= max_change) {
+ int total = add + del;
+
+ total = scale_linear(add + del, graph_width, max_change);
+ if (total < 2 && add && del)
+ /* width >= 2 due to the sanity check */
+ total = 2;
+ if (add < del) {
+ add = scale_linear(add, graph_width, max_change);
+ del = total - add;
+ } else {
+ del = scale_linear(del, graph_width, max_change);
+ add = total - del;
+ }
}
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
@@ -1360,10 +1567,22 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
show_graph(options->file, '-', del, del_c, reset);
fprintf(options->file, "\n");
}
+ for (i = count; i < data->nr; i++) {
+ uintmax_t added = data->files[i]->added;
+ uintmax_t deleted = data->files[i]->deleted;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
+ adds += added;
+ dels += deleted;
+ if (!extra_shown)
+ fprintf(options->file, "%s ...\n", line_prefix);
+ extra_shown = 1;
+ }
fprintf(options->file, "%s", line_prefix);
- fprintf(options->file,
- " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
@@ -1393,8 +1612,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
options->output_prefix_data);
fprintf(options->file, "%s", msg->buf);
}
- fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_numstat(struct diffstat_t *data, struct diff_options *options)
@@ -1446,7 +1664,7 @@ struct dirstat_file {
struct dirstat_dir {
struct dirstat_file *files;
- int alloc, nr, percent, cumulative;
+ int alloc, nr, permille, cumulative;
};
static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
@@ -1493,12 +1711,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
* under this directory (sources == 1).
*/
if (baselen && sources != 1) {
- int permille = this_dir * 1000 / changed;
- if (permille) {
- int percent = permille / 10;
- if (percent >= dir->percent) {
+ if (this_dir) {
+ int permille = this_dir * 1000 / changed;
+ if (permille >= dir->permille) {
fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
- percent, permille % 10, baselen, base);
+ permille / 10, permille % 10, baselen, base);
if (!dir->cumulative)
return 0;
}
@@ -1524,7 +1741,7 @@ static void show_dirstat(struct diff_options *options)
dir.files = NULL;
dir.alloc = 0;
dir.nr = 0;
- dir.percent = options->dirstat_percent;
+ dir.permille = options->dirstat_permille;
dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
changed = 0;
@@ -1532,8 +1749,36 @@ static void show_dirstat(struct diff_options *options)
struct diff_filepair *p = q->queue[i];
const char *name;
unsigned long copied, added, damage;
+ int content_changed;
+
+ name = p->two->path ? p->two->path : p->one->path;
+
+ if (p->one->sha1_valid && p->two->sha1_valid)
+ content_changed = hashcmp(p->one->sha1, p->two->sha1);
+ else
+ content_changed = 1;
+
+ if (!content_changed) {
+ /*
+ * The SHA1 has not changed, so pre-/post-content is
+ * identical. We can therefore skip looking at the
+ * file contents altogether.
+ */
+ damage = 0;
+ goto found_damage;
+ }
- name = p->one->path ? p->one->path : p->two->path;
+ if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) {
+ /*
+ * In --dirstat-by-file mode, we don't really need to
+ * look at the actual file contents at all.
+ * The fact that the SHA1 changed is enough for us to
+ * add this file to the list of results
+ * (with each file contributing equal damage).
+ */
+ damage = 1;
+ goto found_damage;
+ }
if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
diff_populate_filespec(p->one, 0);
@@ -1557,14 +1802,18 @@ static void show_dirstat(struct diff_options *options)
/*
* Original minus copied is the removed material,
* added is the new material. They are both damages
- * made to the preimage. In --dirstat-by-file mode, count
- * damaged files, not damaged lines. This is done by
- * counting only a single damaged line per file.
+ * made to the preimage.
+ * If the resulting damage is zero, we know that
+ * diffcore_count_changes() considers the two entries to
+ * be identical, but since content_changed is true, we
+ * know that there must have been _some_ kind of change,
+ * so we force all entries to have damage > 0.
*/
damage = (p->one->size - copied) + added;
- if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0)
+ if (!damage)
damage = 1;
+found_damage:
ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
dir.files[dir.nr].name = name;
dir.files[dir.nr].changed = damage;
@@ -1581,6 +1830,50 @@ static void show_dirstat(struct diff_options *options)
gather_dirstat(options, &dir, changed, "", 0);
}
+static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options)
+{
+ int i;
+ unsigned long changed;
+ struct dirstat_dir dir;
+
+ if (data->nr == 0)
+ return;
+
+ dir.files = NULL;
+ dir.alloc = 0;
+ dir.nr = 0;
+ dir.permille = options->dirstat_permille;
+ dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+
+ changed = 0;
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+ unsigned long damage = file->added + file->deleted;
+ if (file->is_binary)
+ /*
+ * binary files counts bytes, not lines. Must find some
+ * way to normalize binary bytes vs. textual lines.
+ * The following heuristic assumes that there are 64
+ * bytes per "line".
+ * This is stupid and ugly, but very cheap...
+ */
+ damage = (damage + 63) / 64;
+ ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
+ dir.files[dir.nr].name = file->name;
+ dir.files[dir.nr].changed = damage;
+ changed += damage;
+ dir.nr++;
+ }
+
+ /* This can happen even with many files, if everything was renames */
+ if (!changed)
+ return;
+
+ /* Show all directories with more than x% of the changes */
+ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+ gather_dirstat(options, &dir, changed, "", 0);
+}
+
static void free_diffstat_info(struct diffstat_t *diffstat)
{
int i;
@@ -1630,11 +1923,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l
static void checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
- int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
int marker_size = data->conflict_marker_size;
- const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
- const char *reset = diff_get_color(color_diff, DIFF_RESET);
- const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+ const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+ const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+ const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
char *err;
char *line_prefix = "";
struct strbuf *msgbuf;
@@ -1683,20 +1975,20 @@ static unsigned char *deflate_it(char *data,
{
int bound;
unsigned char *deflated;
- z_stream stream;
+ git_zstream stream;
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
- bound = deflateBound(&stream, size);
+ git_deflate_init(&stream, zlib_compression_level);
+ bound = git_deflate_bound(&stream, size);
deflated = xmalloc(bound);
stream.next_out = deflated;
stream.avail_out = bound;
stream.next_in = (unsigned char *)data;
stream.avail_in = size;
- while (deflate(&stream, Z_FINISH) == Z_OK)
+ while (git_deflate(&stream, Z_FINISH) == Z_OK)
; /* nothing */
- deflateEnd(&stream);
+ git_deflate_end(&stream);
*result_size = stream.total_out;
return deflated;
}
@@ -1828,19 +2120,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one)
return NULL;
diff_filespec_load_driver(one);
- if (!one->driver->textconv)
- return NULL;
-
- if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
- struct notes_cache *c = xmalloc(sizeof(*c));
- struct strbuf name = STRBUF_INIT;
-
- strbuf_addf(&name, "textconv/%s", one->driver->name);
- notes_cache_init(c, name.buf, one->driver->textconv);
- one->driver->textconv_cache = c;
- }
-
- return one->driver;
+ return userdiff_get_textconv(one->driver);
}
static void builtin_diff(const char *name_a,
@@ -1943,7 +2223,11 @@ static void builtin_diff(const char *name_a,
}
}
- if (!DIFF_OPT_TST(o, TEXT) &&
+ if (o->irreversible_delete && lbl[1][0] == '/') {
+ fprintf(o->file, "%s", header.buf);
+ strbuf_reset(&header);
+ goto free_ab_and_return;
+ } else if (!DIFF_OPT_TST(o, TEXT) &&
( (!textconv_one && diff_filespec_is_binary(one)) ||
(!textconv_two && diff_filespec_is_binary(two)) )) {
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
@@ -1963,8 +2247,7 @@ static void builtin_diff(const char *name_a,
fprintf(o->file, "%sBinary files %s and %s differ\n",
line_prefix, lbl[0], lbl[1]);
o->found_changes = 1;
- }
- else {
+ } else {
/* Crazy xdl interfaces.. */
const char *diffopts = getenv("GIT_DIFF_OPTS");
xpparam_t xpp;
@@ -1972,7 +2255,7 @@ static void builtin_diff(const char *name_a,
struct emit_callback ecbdata;
const struct userdiff_funcname *pe;
- if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+ if (must_show_header) {
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
}
@@ -1988,7 +2271,7 @@ static void builtin_diff(const char *name_a,
memset(&xecfg, 0, sizeof(xecfg));
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.label_path = lbl;
- ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+ ecbdata.color_diff = want_color(o->use_color);
ecbdata.found_changesp = &o->found_changes;
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
@@ -1999,6 +2282,8 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
xecfg.flags = XDL_EMIT_FUNCNAMES;
+ if (DIFF_OPT_TST(o, FUNCCONTEXT))
+ xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
if (pe)
xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
if (!diffopts)
@@ -2036,7 +2321,7 @@ static void builtin_diff(const char *name_a,
break;
}
}
- if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+ if (want_color(o->use_color)) {
struct diff_words_style *st = ecbdata.diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
@@ -2079,33 +2364,37 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
data->is_unmerged = 1;
return;
}
- if (complete_rewrite) {
+
+ if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
+ data->is_binary = 1;
+ data->added = diff_filespec_size(two);
+ data->deleted = diff_filespec_size(one);
+ }
+
+ else if (complete_rewrite) {
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
- goto free_and_return;
}
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
- if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
- data->is_binary = 1;
- data->added = mf2.size;
- data->deleted = mf1.size;
- } else {
+ else {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = o->xdl_opts;
+ xecfg.ctxlen = o->context;
+ xecfg.interhunkctxlen = o->interhunkcontext;
xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
&xpp, &xecfg);
}
- free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
}
@@ -2684,7 +2973,7 @@ static void run_diff_cmd(const char *pgm,
*/
fill_metainfo(msg, name, other, one, two, o, p,
&must_show_header,
- DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+ want_color(o->use_color) && !pgm);
xfrm_msg = msg->len ? msg->buf : NULL;
}
@@ -2845,13 +3134,12 @@ void diff_setup(struct diff_options *options)
options->line_termination = '\n';
options->break_opt = -1;
options->rename_limit = -1;
- options->dirstat_percent = 3;
+ options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
options->change = diff_change;
options->add_remove = diff_addremove;
- if (diff_use_color_default > 0)
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = diff_use_color_default;
options->detect_rename = diff_detect_rename_default;
if (diff_no_prefix) {
@@ -3059,6 +3347,8 @@ static int stat_opt(struct diff_options *options, const char **av)
char *end;
int width = options->stat_width;
int name_width = options->stat_name_width;
+ int graph_width = options->stat_graph_width;
+ int count = options->stat_count;
int argcount = 1;
arg += strlen("--stat");
@@ -3086,12 +3376,34 @@ static int stat_opt(struct diff_options *options, const char **av)
name_width = strtoul(av[1], &end, 10);
argcount = 2;
}
+ } else if (!prefixcmp(arg, "-graph-width")) {
+ arg += strlen("-graph-width");
+ if (*arg == '=')
+ graph_width = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-graph-width' requires a value");
+ else if (!*arg) {
+ graph_width = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
+ } else if (!prefixcmp(arg, "-count")) {
+ arg += strlen("-count");
+ if (*arg == '=')
+ count = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-count' requires a value");
+ else if (!*arg) {
+ count = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
}
break;
case '=':
width = strtoul(arg+1, &end, 10);
if (*end == ',')
name_width = strtoul(end+1, &end, 10);
+ if (*end == ',')
+ count = strtoul(end+1, &end, 10);
}
/* Important! This checks all the error cases! */
@@ -3099,10 +3411,27 @@ static int stat_opt(struct diff_options *options, const char **av)
return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
options->stat_name_width = name_width;
+ options->stat_graph_width = graph_width;
options->stat_width = width;
+ options->stat_count = count;
return argcount;
}
+static int parse_dirstat_opt(struct diff_options *options, const char *params)
+{
+ struct strbuf errmsg = STRBUF_INIT;
+ if (parse_dirstat_params(options, params, &errmsg))
+ die(_("Failed to parse --dirstat/-X option parameter:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ /*
+ * The caller knows a dirstat-related option is given from the command
+ * line; allow it to say "return this_function();"
+ */
+ options->output_format |= DIFF_FORMAT_DIRSTAT;
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
@@ -3122,15 +3451,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->output_format |= DIFF_FORMAT_NUMSTAT;
else if (!strcmp(arg, "--shortstat"))
options->output_format |= DIFF_FORMAT_SHORTSTAT;
- else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- else if (!strcmp(arg, "--cumulative")) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
- } else if (opt_arg(arg, 0, "dirstat-by-file",
- &options->dirstat_percent)) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
+ return parse_dirstat_opt(options, "");
+ else if (!prefixcmp(arg, "-X"))
+ return parse_dirstat_opt(options, arg + 2);
+ else if (!prefixcmp(arg, "--dirstat="))
+ return parse_dirstat_opt(options, arg + 10);
+ else if (!strcmp(arg, "--cumulative"))
+ return parse_dirstat_opt(options, "cumulative");
+ else if (!strcmp(arg, "--dirstat-by-file"))
+ return parse_dirstat_opt(options, "files");
+ else if (!prefixcmp(arg, "--dirstat-by-file=")) {
+ parse_dirstat_opt(options, "files");
+ return parse_dirstat_opt(options, arg + 18);
}
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
@@ -3145,7 +3478,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "-s"))
options->output_format |= DIFF_FORMAT_NO_OUTPUT;
else if (!prefixcmp(arg, "--stat"))
- /* --stat, --stat-width, or --stat-name-width */
+ /* --stat, --stat-width, --stat-name-width, or --stat-count */
return stat_opt(options, av);
/* renames options */
@@ -3160,6 +3493,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
return error("invalid argument to -M: %s", arg+2);
options->detect_rename = DIFF_DETECT_RENAME;
}
+ else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
+ options->irreversible_delete = 1;
+ }
else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
!strcmp(arg, "--find-copies")) {
if (options->detect_rename == DIFF_DETECT_COPY)
@@ -3178,6 +3514,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
/* xdiff options */
+ else if (!strcmp(arg, "--minimal"))
+ DIFF_XDL_SET(options, NEED_MINIMAL);
+ else if (!strcmp(arg, "--no-minimal"))
+ DIFF_XDL_CLR(options, NEED_MINIMAL);
else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
DIFF_XDL_SET(options, IGNORE_WHITESPACE);
else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
@@ -3186,6 +3526,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
else if (!strcmp(arg, "--patience"))
DIFF_XDL_SET(options, PATIENCE_DIFF);
+ else if (!strcmp(arg, "--histogram"))
+ DIFF_XDL_SET(options, HISTOGRAM_DIFF);
/* flags options */
else if (!strcmp(arg, "--binary")) {
@@ -3203,24 +3545,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--follow"))
DIFF_OPT_SET(options, FOLLOW_RENAMES);
else if (!strcmp(arg, "--color"))
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
else if (!prefixcmp(arg, "--color=")) {
- int value = git_config_colorbool(NULL, arg+8, -1);
- if (value == 0)
- DIFF_OPT_CLR(options, COLOR_DIFF);
- else if (value > 0)
- DIFF_OPT_SET(options, COLOR_DIFF);
- else
+ int value = git_config_colorbool(NULL, arg+8);
+ if (value < 0)
return error("option `color' expects \"always\", \"auto\", or \"never\"");
+ options->use_color = value;
}
else if (!strcmp(arg, "--no-color"))
- DIFF_OPT_CLR(options, COLOR_DIFF);
+ options->use_color = 0;
else if (!strcmp(arg, "--color-words")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
}
else if (!prefixcmp(arg, "--color-words=")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
options->word_regex = arg + 14;
}
@@ -3233,7 +3572,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
if (!strcmp(type, "plain"))
options->word_diff = DIFF_WORDS_PLAIN;
else if (!strcmp(type, "color")) {
- DIFF_OPT_SET(options, COLOR_DIFF);
+ options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
}
else if (!strcmp(type, "porcelain"))
@@ -3324,6 +3663,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (opt_arg(arg, '\0', "inter-hunk-context",
&options->interhunkcontext))
;
+ else if (!strcmp(arg, "-W"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--function-context"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--no-function-context"))
+ DIFF_OPT_CLR(options, FUNCCONTEXT);
else if ((argcount = parse_long_opt("output", av, &optarg))) {
options->file = fopen(optarg, "w");
if (!options->file)
@@ -3947,11 +4292,34 @@ static int is_summary_empty(const struct diff_queue_struct *q)
return 1;
}
+static const char rename_limit_warning[] =
+"inexact rename detection was skipped due to too many files.";
+
+static const char degrade_cc_to_c_warning[] =
+"only found copies from modified paths due to too many files.";
+
+static const char rename_limit_advice[] =
+"you may want to set your %s variable to at least "
+"%d and retry the command.";
+
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
+{
+ if (degraded_cc)
+ warning(degrade_cc_to_c_warning);
+ else if (needed)
+ warning(rename_limit_warning);
+ else
+ return;
+ if (0 < needed && needed < 32767)
+ warning(rename_limit_advice, varname, needed);
+}
+
void diff_flush(struct diff_options *options)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i, output_format = options->output_format;
int separator = 0;
+ int dirstat_by_line = 0;
/*
* Order: raw, stat, summary, patch
@@ -3972,7 +4340,11 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
+ if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
+ dirstat_by_line = 1;
+
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
+ dirstat_by_line) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -3987,10 +4359,12 @@ void diff_flush(struct diff_options *options)
show_stats(&diffstat, options);
if (output_format & DIFF_FORMAT_SHORTSTAT)
show_shortstats(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIRSTAT)
+ show_dirstat_by_line(&diffstat, options);
free_diffstat_info(&diffstat);
separator++;
}
- if (output_format & DIFF_FORMAT_DIRSTAT)
+ if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line)
show_dirstat(options);
if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
@@ -4228,6 +4602,10 @@ void diffcore_std(struct diff_options *options)
int diff_result_code(struct diff_options *opt, int status)
{
int result = 0;
+
+ diff_warn_rename_limit("diff.renamelimit",
+ opt->needed_rename_limit,
+ opt->degraded_cc_to_c);
if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
!(opt->output_format & DIFF_FORMAT_CHECKDIFF))
return status;
@@ -4240,6 +4618,13 @@ int diff_result_code(struct diff_options *opt, int status)
return result;
}
+int diff_can_quit_early(struct diff_options *opt)
+{
+ return (DIFF_OPT_TST(opt, QUICK) &&
+ !opt->filter &&
+ DIFF_OPT_TST(opt, HAS_CHANGES));
+}
+
/*
* Shall changes to this submodule be ignored?
*
@@ -4341,20 +4726,20 @@ void diff_change(struct diff_options *options,
DIFF_OPT_SET(options, HAS_CHANGES);
}
-void diff_unmerge(struct diff_options *options,
- const char *path,
- unsigned mode, const unsigned char *sha1)
+struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
{
+ struct diff_filepair *pair;
struct diff_filespec *one, *two;
if (options->prefix &&
strncmp(path, options->prefix, options->prefix_length))
- return;
+ return NULL;
one = alloc_filespec(path);
two = alloc_filespec(path);
- fill_filespec(one, sha1, mode);
- diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
+ pair = diff_queue(&diff_queued_diff, one, two);
+ pair->is_unmerged = 1;
+ return pair;
}
static char *run_textconv(const char *pgm, struct diff_filespec *spec,