aboutsummaryrefslogtreecommitdiffstats
path: root/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'diff.c')
-rw-r--r--diff.c1029
1 files changed, 603 insertions, 426 deletions
diff --git a/diff.c b/diff.c
index 2253ec8802..1054a4b732 100644
--- a/diff.c
+++ b/diff.c
@@ -18,6 +18,7 @@
#include "submodule-config.h"
#include "submodule.h"
#include "hashmap.h"
+#include "mem-pool.h"
#include "ll-merge.h"
#include "string-list.h"
#include "strvec.h"
@@ -26,6 +27,8 @@
#include "parse-options.h"
#include "help.h"
#include "promisor-remote.h"
+#include "dir.h"
+#include "strmap.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
@@ -35,7 +38,7 @@
static int diff_detect_rename_default;
static int diff_indent_heuristic = 1;
-static int diff_rename_limit_default = 400;
+static int diff_rename_limit_default = 1000;
static int diff_suppress_blank_empty;
static int diff_use_color_default = -1;
static int diff_color_moved_default;
@@ -261,7 +264,8 @@ void init_diff_ui_defaults(void)
diff_detect_rename_default = DIFF_DETECT_RENAME;
}
-int git_diff_heuristic_config(const char *var, const char *value, void *cb)
+int git_diff_heuristic_config(const char *var, const char *value,
+ void *cb UNUSED)
{
if (!strcmp(var, "diff.indentheuristic"))
diff_indent_heuristic = git_config_bool(var, value);
@@ -772,15 +776,16 @@ struct emitted_diff_symbol {
int flags;
int indent_off; /* Offset to first non-whitespace character */
int indent_width; /* The visual width of the indentation */
+ unsigned id;
enum diff_symbol s;
};
-#define EMITTED_DIFF_SYMBOL_INIT {NULL}
+#define EMITTED_DIFF_SYMBOL_INIT { 0 }
struct emitted_diff_symbols {
struct emitted_diff_symbol *buf;
int nr, alloc;
};
-#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}
+#define EMITTED_DIFF_SYMBOLS_INIT { 0 }
static void append_emitted_diff_symbol(struct diff_options *o,
struct emitted_diff_symbol *e)
@@ -796,10 +801,18 @@ static void append_emitted_diff_symbol(struct diff_options *o,
f->line = e->line ? xmemdupz(e->line, e->len) : NULL;
}
+static void free_emitted_diff_symbols(struct emitted_diff_symbols *e)
+{
+ if (!e)
+ return;
+ free(e->buf);
+ free(e);
+}
+
struct moved_entry {
- struct hashmap_entry ent;
const struct emitted_diff_symbol *es;
struct moved_entry *next_line;
+ struct moved_entry *next_match;
};
struct moved_block {
@@ -807,11 +820,6 @@ struct moved_block {
int wsd; /* The whitespace delta of this block */
};
-static void moved_block_clear(struct moved_block *b)
-{
- memset(b, 0, sizeof(*b));
-}
-
#define INDENT_BLANKLINE INT_MIN
static void fill_es_indent_data(struct emitted_diff_symbol *es)
@@ -855,79 +863,41 @@ static void fill_es_indent_data(struct emitted_diff_symbol *es)
}
static int compute_ws_delta(const struct emitted_diff_symbol *a,
- const struct emitted_diff_symbol *b,
- int *out)
-{
- int a_len = a->len,
- b_len = b->len,
- a_off = a->indent_off,
- a_width = a->indent_width,
- b_off = b->indent_off,
+ const struct emitted_diff_symbol *b)
+{
+ int a_width = a->indent_width,
b_width = b->indent_width;
- int delta;
- if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE) {
- *out = INDENT_BLANKLINE;
- return 1;
- }
-
- if (a->s == DIFF_SYMBOL_PLUS)
- delta = a_width - b_width;
- else
- delta = b_width - a_width;
-
- if (a_len - a_off != b_len - b_off ||
- memcmp(a->line + a_off, b->line + b_off, a_len - a_off))
- return 0;
+ if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE)
+ return INDENT_BLANKLINE;
- *out = delta;
-
- return 1;
+ return a_width - b_width;
}
-static int cmp_in_block_with_wsd(const struct diff_options *o,
- const struct moved_entry *cur,
- const struct moved_entry *match,
- struct moved_block *pmb,
- int n)
-{
- struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
- int al = cur->es->len, bl = match->es->len, cl = l->len;
- const char *a = cur->es->line,
- *b = match->es->line,
- *c = l->line;
- int a_off = cur->es->indent_off,
- a_width = cur->es->indent_width,
- c_off = l->indent_off,
- c_width = l->indent_width;
+static int cmp_in_block_with_wsd(const struct moved_entry *cur,
+ const struct emitted_diff_symbol *l,
+ struct moved_block *pmb)
+{
+ int a_width = cur->es->indent_width, b_width = l->indent_width;
int delta;
- /*
- * We need to check if 'cur' is equal to 'match'. As those
- * are from the same (+/-) side, we do not need to adjust for
- * indent changes. However these were found using fuzzy
- * matching so we do have to check if they are equal. Here we
- * just check the lengths. We delay calling memcmp() to check
- * the contents until later as if the length comparison for a
- * and c fails we can avoid the call all together.
- */
- if (al != bl)
+ /* The text of each line must match */
+ if (cur->es->id != l->id)
return 1;
- /* If 'l' and 'cur' are both blank then they match. */
- if (a_width == INDENT_BLANKLINE && c_width == INDENT_BLANKLINE)
+ /*
+ * If 'l' and 'cur' are both blank then we don't need to check the
+ * indent. We only need to check cur as we know the strings match.
+ * */
+ if (a_width == INDENT_BLANKLINE)
return 0;
/*
* The indent changes of the block are known and stored in pmb->wsd;
* however we need to check if the indent changes of the current line
- * match those of the current block and that the text of 'l' and 'cur'
- * after the indentation match.
+ * match those of the current block.
*/
- if (cur->es->s == DIFF_SYMBOL_PLUS)
- delta = a_width - c_width;
- else
- delta = c_width - a_width;
+ delta = b_width - a_width;
/*
* If the previous lines of this block were all blank then set its
@@ -936,166 +906,165 @@ static int cmp_in_block_with_wsd(const struct diff_options *o,
if (pmb->wsd == INDENT_BLANKLINE)
pmb->wsd = delta;
- return !(delta == pmb->wsd && al - a_off == cl - c_off &&
- !memcmp(a, b, al) && !
- memcmp(a + a_off, c + c_off, al - a_off));
+ return delta != pmb->wsd;
}
-static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
- const struct hashmap_entry *eptr,
- const struct hashmap_entry *entry_or_key,
- const void *keydata)
+struct interned_diff_symbol {
+ struct hashmap_entry ent;
+ struct emitted_diff_symbol *es;
+};
+
+static int interned_diff_symbol_cmp(const void *hashmap_cmp_fn_data,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata UNUSED)
{
const struct diff_options *diffopt = hashmap_cmp_fn_data;
- const struct moved_entry *a, *b;
+ const struct emitted_diff_symbol *a, *b;
unsigned flags = diffopt->color_moved_ws_handling
& XDF_WHITESPACE_FLAGS;
- a = container_of(eptr, const struct moved_entry, ent);
- b = container_of(entry_or_key, const struct moved_entry, ent);
+ a = container_of(eptr, const struct interned_diff_symbol, ent)->es;
+ b = container_of(entry_or_key, const struct interned_diff_symbol, ent)->es;
- if (diffopt->color_moved_ws_handling &
- COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
- /*
- * As there is not specific white space config given,
- * we'd need to check for a new block, so ignore all
- * white space. The setup of the white space
- * configuration for the next block is done else where
- */
- flags |= XDF_IGNORE_WHITESPACE;
-
- return !xdiff_compare_lines(a->es->line, a->es->len,
- b->es->line, b->es->len,
- flags);
+ return !xdiff_compare_lines(a->line + a->indent_off,
+ a->len - a->indent_off,
+ b->line + b->indent_off,
+ b->len - b->indent_off, flags);
}
-static struct moved_entry *prepare_entry(struct diff_options *o,
- int line_no)
+static void prepare_entry(struct diff_options *o, struct emitted_diff_symbol *l,
+ struct interned_diff_symbol *s)
{
- struct moved_entry *ret = xmalloc(sizeof(*ret));
- struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS;
- unsigned int hash = xdiff_hash_string(l->line, l->len, flags);
+ unsigned int hash = xdiff_hash_string(l->line + l->indent_off,
+ l->len - l->indent_off, flags);
- hashmap_entry_init(&ret->ent, hash);
- ret->es = l;
- ret->next_line = NULL;
-
- return ret;
+ hashmap_entry_init(&s->ent, hash);
+ s->es = l;
}
-static void add_lines_to_move_detection(struct diff_options *o,
- struct hashmap *add_lines,
- struct hashmap *del_lines)
+struct moved_entry_list {
+ struct moved_entry *add, *del;
+};
+
+static struct moved_entry_list *add_lines_to_move_detection(struct diff_options *o,
+ struct mem_pool *entry_mem_pool)
{
struct moved_entry *prev_line = NULL;
-
+ struct mem_pool interned_pool;
+ struct hashmap interned_map;
+ struct moved_entry_list *entry_list = NULL;
+ size_t entry_list_alloc = 0;
+ unsigned id = 0;
int n;
+
+ hashmap_init(&interned_map, interned_diff_symbol_cmp, o, 8096);
+ mem_pool_init(&interned_pool, 1024 * 1024);
+
for (n = 0; n < o->emitted_symbols->nr; n++) {
- struct hashmap *hm;
- struct moved_entry *key;
+ struct interned_diff_symbol key;
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
+ struct interned_diff_symbol *s;
+ struct moved_entry *entry;
- switch (o->emitted_symbols->buf[n].s) {
- case DIFF_SYMBOL_PLUS:
- hm = add_lines;
- break;
- case DIFF_SYMBOL_MINUS:
- hm = del_lines;
- break;
- default:
+ if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS) {
prev_line = NULL;
continue;
}
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
- fill_es_indent_data(&o->emitted_symbols->buf[n]);
- key = prepare_entry(o, n);
- if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
- prev_line->next_line = key;
+ fill_es_indent_data(l);
- hashmap_add(hm, &key->ent);
- prev_line = key;
+ prepare_entry(o, l, &key);
+ s = hashmap_get_entry(&interned_map, &key, ent, &key.ent);
+ if (s) {
+ l->id = s->es->id;
+ } else {
+ l->id = id;
+ ALLOC_GROW_BY(entry_list, id, 1, entry_list_alloc);
+ hashmap_add(&interned_map,
+ memcpy(mem_pool_alloc(&interned_pool,
+ sizeof(key)),
+ &key, sizeof(key)));
+ }
+ entry = mem_pool_alloc(entry_mem_pool, sizeof(*entry));
+ entry->es = l;
+ entry->next_line = NULL;
+ if (prev_line && prev_line->es->s == l->s)
+ prev_line->next_line = entry;
+ prev_line = entry;
+ if (l->s == DIFF_SYMBOL_PLUS) {
+ entry->next_match = entry_list[l->id].add;
+ entry_list[l->id].add = entry;
+ } else {
+ entry->next_match = entry_list[l->id].del;
+ entry_list[l->id].del = entry;
+ }
}
+
+ hashmap_clear(&interned_map);
+ mem_pool_discard(&interned_pool, 0);
+
+ return entry_list;
}
static void pmb_advance_or_null(struct diff_options *o,
- struct moved_entry *match,
- struct hashmap *hm,
+ struct emitted_diff_symbol *l,
struct moved_block *pmb,
- int pmb_nr)
+ int *pmb_nr)
{
- int i;
- for (i = 0; i < pmb_nr; i++) {
+ int i, j;
+
+ for (i = 0, j = 0; i < *pmb_nr; i++) {
+ int match;
struct moved_entry *prev = pmb[i].match;
struct moved_entry *cur = (prev && prev->next_line) ?
prev->next_line : NULL;
- if (cur && !hm->cmpfn(o, &cur->ent, &match->ent, NULL)) {
- pmb[i].match = cur;
- } else {
- pmb[i].match = NULL;
- }
- }
-}
-static void pmb_advance_or_null_multi_match(struct diff_options *o,
- struct moved_entry *match,
- struct hashmap *hm,
- struct moved_block *pmb,
- int pmb_nr, int n)
-{
- int i;
- char *got_match = xcalloc(1, pmb_nr);
-
- hashmap_for_each_entry_from(hm, match, ent) {
- for (i = 0; i < pmb_nr; i++) {
- struct moved_entry *prev = pmb[i].match;
- struct moved_entry *cur = (prev && prev->next_line) ?
- prev->next_line : NULL;
- if (!cur)
- continue;
- if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n))
- got_match[i] |= 1;
- }
- }
+ if (o->color_moved_ws_handling &
+ COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+ match = cur &&
+ !cmp_in_block_with_wsd(cur, l, &pmb[i]);
+ else
+ match = cur && cur->es->id == l->id;
- for (i = 0; i < pmb_nr; i++) {
- if (got_match[i]) {
- /* Advance to the next line */
- pmb[i].match = pmb[i].match->next_line;
- } else {
- moved_block_clear(&pmb[i]);
+ if (match) {
+ pmb[j] = pmb[i];
+ pmb[j++].match = cur;
}
}
-
- free(got_match);
+ *pmb_nr = j;
}
-static int shrink_potential_moved_blocks(struct moved_block *pmb,
- int pmb_nr)
-{
- int lp, rp;
-
- /* Shrink the set of potential block to the remaining running */
- for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
- while (lp < pmb_nr && pmb[lp].match)
- lp++;
- /* lp points at the first NULL now */
+static void fill_potential_moved_blocks(struct diff_options *o,
+ struct moved_entry *match,
+ struct emitted_diff_symbol *l,
+ struct moved_block **pmb_p,
+ int *pmb_alloc_p, int *pmb_nr_p)
- while (rp > -1 && !pmb[rp].match)
- rp--;
- /* rp points at the last non-NULL */
+{
+ struct moved_block *pmb = *pmb_p;
+ int pmb_alloc = *pmb_alloc_p, pmb_nr = *pmb_nr_p;
- if (lp < pmb_nr && rp > -1 && lp < rp) {
- pmb[lp] = pmb[rp];
- memset(&pmb[rp], 0, sizeof(pmb[rp]));
- rp--;
- lp++;
- }
+ /*
+ * The current line is the start of a new block.
+ * Setup the set of potential blocks.
+ */
+ for (; match; match = match->next_match) {
+ ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
+ if (o->color_moved_ws_handling &
+ COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+ pmb[pmb_nr].wsd = compute_ws_delta(l, match->es);
+ else
+ pmb[pmb_nr].wsd = 0;
+ pmb[pmb_nr++].match = match;
}
- /* Remember the number of running sets */
- return rp + 1;
+ *pmb_p = pmb;
+ *pmb_alloc_p = pmb_alloc;
+ *pmb_nr_p = pmb_nr;
}
/*
@@ -1114,6 +1083,8 @@ static int shrink_potential_moved_blocks(struct moved_block *pmb,
* NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
* Think of a way to unify them.
*/
+#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
+ (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
static int adjust_last_block(struct diff_options *o, int n, int block_length)
{
int i, alnum_count = 0;
@@ -1130,95 +1101,85 @@ static int adjust_last_block(struct diff_options *o, int n, int block_length)
}
}
for (i = 1; i < block_length + 1; i++)
- o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
+ o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK;
return 0;
}
/* Find blocks of moved code, delegate actual coloring decision to helper */
static void mark_color_as_moved(struct diff_options *o,
- struct hashmap *add_lines,
- struct hashmap *del_lines)
+ struct moved_entry_list *entry_list)
{
struct moved_block *pmb = NULL; /* potentially moved blocks */
int pmb_nr = 0, pmb_alloc = 0;
int n, flipped_block = 0, block_length = 0;
+ enum diff_symbol moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
for (n = 0; n < o->emitted_symbols->nr; n++) {
- struct hashmap *hm = NULL;
- struct moved_entry *key;
struct moved_entry *match = NULL;
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
- enum diff_symbol last_symbol = 0;
switch (l->s) {
case DIFF_SYMBOL_PLUS:
- hm = del_lines;
- key = prepare_entry(o, n);
- match = hashmap_get_entry(hm, key, ent, NULL);
- free(key);
+ match = entry_list[l->id].del;
break;
case DIFF_SYMBOL_MINUS:
- hm = add_lines;
- key = prepare_entry(o, n);
- match = hashmap_get_entry(hm, key, ent, NULL);
- free(key);
+ match = entry_list[l->id].add;
break;
default:
flipped_block = 0;
}
- if (!match) {
- int i;
-
- adjust_last_block(o, n, block_length);
- for(i = 0; i < pmb_nr; i++)
- moved_block_clear(&pmb[i]);
+ if (pmb_nr && (!match || l->s != moved_symbol)) {
+ if (!adjust_last_block(o, n, block_length) &&
+ block_length > 1) {
+ /*
+ * Rewind in case there is another match
+ * starting at the second line of the block
+ */
+ match = NULL;
+ n -= block_length;
+ }
pmb_nr = 0;
block_length = 0;
flipped_block = 0;
- last_symbol = l->s;
+ }
+ if (!match) {
+ moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
continue;
}
if (o->color_moved == COLOR_MOVED_PLAIN) {
- last_symbol = l->s;
l->flags |= DIFF_SYMBOL_MOVED_LINE;
continue;
}
- if (o->color_moved_ws_handling &
- COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
- pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n);
- else
- pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
-
- pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
+ pmb_advance_or_null(o, l, pmb, &pmb_nr);
if (pmb_nr == 0) {
- /*
- * The current line is the start of a new block.
- * Setup the set of potential blocks.
- */
- hashmap_for_each_entry_from(hm, match, ent) {
- ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
- if (o->color_moved_ws_handling &
- COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
- if (compute_ws_delta(l, match->es,
- &pmb[pmb_nr].wsd))
- pmb[pmb_nr++].match = match;
- } else {
- pmb[pmb_nr].wsd = 0;
- pmb[pmb_nr++].match = match;
- }
- }
+ int contiguous = adjust_last_block(o, n, block_length);
+
+ if (!contiguous && block_length > 1)
+ /*
+ * Rewind in case there is another match
+ * starting at the second line of the block
+ */
+ n -= block_length;
+ else
+ fill_potential_moved_blocks(o, match, l,
+ &pmb, &pmb_alloc,
+ &pmb_nr);
- if (adjust_last_block(o, n, block_length) &&
- pmb_nr && last_symbol != l->s)
+ if (contiguous && pmb_nr && moved_symbol == l->s)
flipped_block = (flipped_block + 1) % 2;
else
flipped_block = 0;
+ if (pmb_nr)
+ moved_symbol = l->s;
+ else
+ moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
+
block_length = 0;
}
@@ -1228,17 +1189,12 @@ static void mark_color_as_moved(struct diff_options *o,
if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
}
- last_symbol = l->s;
}
adjust_last_block(o, n, block_length);
- for(n = 0; n < pmb_nr; n++)
- moved_block_clear(&pmb[n]);
free(pmb);
}
-#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
- (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
static void dim_moved_lines(struct diff_options *o)
{
int n;
@@ -1334,7 +1290,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
{
static const char *nneof = " No newline at end of file\n";
const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
- struct strbuf sb = STRBUF_INIT;
enum diff_symbol s = eds->s;
const char *line = eds->line;
@@ -1566,13 +1521,14 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
default:
BUG("unknown diff symbol");
}
- strbuf_release(&sb);
}
static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s,
const char *line, int len, unsigned flags)
{
- struct emitted_diff_symbol e = {line, len, flags, 0, 0, s};
+ struct emitted_diff_symbol e = {
+ .line = line, .len = len, .flags = flags, .s = s
+ };
if (o->emitted_symbols)
append_emitted_diff_symbol(o, &e);
@@ -2053,7 +2009,7 @@ static void fn_out_diff_words_aux(void *priv,
static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
int *begin, int *end)
{
- if (word_regex && *begin < buffer->size) {
+ while (word_regex && *begin < buffer->size) {
regmatch_t match[1];
if (!regexec_buf(word_regex, buffer->ptr + *begin,
buffer->size - *begin, 1, match, 0)) {
@@ -2061,9 +2017,13 @@ static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
'\n', match[0].rm_eo - match[0].rm_so);
*end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
*begin += match[0].rm_so;
- return *begin >= *end;
+ if (*begin == *end)
+ (*begin)++;
+ else
+ return *begin > *end;
+ } else {
+ return -1;
}
- return -1;
}
/* find the next word */
@@ -2233,14 +2193,12 @@ static void init_diff_words_data(struct emit_callback *ecbdata,
struct diff_options *o = xmalloc(sizeof(struct diff_options));
memcpy(o, orig_opts, sizeof(struct diff_options));
- ecbdata->diff_words =
- xcalloc(1, sizeof(struct diff_words_data));
+ CALLOC_ARRAY(ecbdata->diff_words, 1);
ecbdata->diff_words->type = o->word_diff;
ecbdata->diff_words->opt = o;
if (orig_opts->emitted_symbols)
- o->emitted_symbols =
- xcalloc(1, sizeof(struct emitted_diff_symbols));
+ CALLOC_ARRAY(o->emitted_symbols, 1);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one, o->repo->index);
@@ -2276,7 +2234,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
{
if (ecbdata->diff_words) {
diff_words_flush(ecbdata);
- free (ecbdata->diff_words->opt->emitted_symbols);
+ free_emitted_diff_symbols(ecbdata->diff_words->opt->emitted_symbols);
free (ecbdata->diff_words->opt);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
@@ -2338,7 +2296,7 @@ static void find_lno(const char *line, struct emit_callback *ecbdata)
ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
}
-static void fn_out_consume(void *priv, char *line, unsigned long len)
+static int fn_out_consume(void *priv, char *line, unsigned long len)
{
struct emit_callback *ecbdata = priv;
struct diff_options *o = ecbdata->opt;
@@ -2374,7 +2332,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
len = sane_truncate_line(line, len);
find_lno(line, ecbdata);
emit_hunk_header(ecbdata, line, len);
- return;
+ return 0;
}
if (ecbdata->diff_words) {
@@ -2384,11 +2342,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
if (line[0] == '-') {
diff_words_append(line, len,
&ecbdata->diff_words->minus);
- return;
+ return 0;
} else if (line[0] == '+') {
diff_words_append(line, len,
&ecbdata->diff_words->plus);
- return;
+ return 0;
} else if (starts_with(line, "\\ ")) {
/*
* Eat the "no newline at eof" marker as if we
@@ -2397,11 +2355,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
* defer processing. If this is the end of
* preimage, more "+" lines may come after it.
*/
- return;
+ return 0;
}
diff_words_flush(ecbdata);
emit_diff_symbol(o, s, line, len, 0);
- return;
+ return 0;
}
switch (line[0]) {
@@ -2425,6 +2383,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
line, len, 0);
break;
}
+ return 0;
}
static void pprint_rename(struct strbuf *name, const char *a, const char *b)
@@ -2509,7 +2468,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
const char *name_b)
{
struct diffstat_file *x;
- x = xcalloc(1, sizeof(*x));
+ CALLOC_ARRAY(x, 1);
ALLOC_GROW(diffstat->files, diffstat->nr + 1, diffstat->alloc);
diffstat->files[diffstat->nr++] = x;
if (name_b) {
@@ -2524,15 +2483,19 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
return x;
}
-static void diffstat_consume(void *priv, char *line, unsigned long len)
+static int diffstat_consume(void *priv, char *line, unsigned long len)
{
struct diffstat_t *diffstat = priv;
struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+ if (!len)
+ BUG("xdiff fed us an empty line");
+
if (line[0] == '+')
x->added++;
else if (line[0] == '-')
x->deleted++;
+ return 0;
}
const char mime_boundary_leader[] = "------------";
@@ -2661,7 +2624,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
continue;
}
fill_print_name(file);
- len = strlen(file->print_name);
+ len = utf8_strwidth(file->print_name);
if (max_len < len)
max_len = len;
@@ -2714,6 +2677,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
* making the line longer than the maximum width.
*/
+ /*
+ * NEEDSWORK: line_prefix is often used for "log --graph" output
+ * and contains ANSI-colored string. utf8_strnwidth() should be
+ * used to correctly count the display width instead of strlen().
+ */
if (options->stat_width == -1)
width = term_columns() - strlen(line_prefix);
else
@@ -2775,7 +2743,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
char *name = file->print_name;
uintmax_t added = file->added;
uintmax_t deleted = file->deleted;
- int name_len;
+ int name_len, padding;
if (!file->is_interesting && (added + deleted == 0))
continue;
@@ -2784,20 +2752,34 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
* "scale" the filename
*/
len = name_width;
- name_len = strlen(name);
+ name_len = utf8_strwidth(name);
if (name_width < name_len) {
char *slash;
prefix = "...";
len -= 3;
+ /*
+ * NEEDSWORK: (name_len - len) counts the display
+ * width, which would be shorter than the byte
+ * length of the corresponding substring.
+ * Advancing "name" by that number of bytes does
+ * *NOT* skip over that many columns, so it is
+ * very likely that chomping the pathname at the
+ * slash we will find starting from "name" will
+ * leave the resulting string still too long.
+ */
name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
+ padding = len - utf8_strwidth(name);
+ if (padding < 0)
+ padding = 0;
if (file->is_binary) {
- strbuf_addf(&out, " %s%-*s |", prefix, len, name);
- strbuf_addf(&out, " %*s", number_width, "Bin");
+ strbuf_addf(&out, " %s%s%*s | %*s",
+ prefix, name, padding, "",
+ number_width, "Bin");
if (!added && !deleted) {
strbuf_addch(&out, '\n');
emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
@@ -2817,8 +2799,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
continue;
}
else if (file->is_unmerged) {
- strbuf_addf(&out, " %s%-*s |", prefix, len, name);
- strbuf_addstr(&out, " Unmerged\n");
+ strbuf_addf(&out, " %s%s%*s | %*s",
+ prefix, name, padding, "",
+ number_width, "Unmerged");
emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
out.buf, out.len, 0);
strbuf_reset(&out);
@@ -2844,10 +2827,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
add = total - del;
}
}
- strbuf_addf(&out, " %s%-*s |", prefix, len, name);
- strbuf_addf(&out, " %*"PRIuMAX"%s",
- number_width, added + deleted,
- added + deleted ? " " : "");
+ strbuf_addf(&out, " %s%s%*s | %*"PRIuMAX"%s",
+ prefix, name, padding, "",
+ number_width, added + deleted,
+ added + deleted ? " " : "");
show_graph(&out, '+', add, add_c, reset);
show_graph(&out, '-', del, del_c, reset);
strbuf_addch(&out, '\n');
@@ -3210,7 +3193,7 @@ static void checkdiff_consume_hunk(void *priv,
data->lineno = nb - 1;
}
-static void checkdiff_consume(void *priv, char *line, unsigned long len)
+static int checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
int marker_size = data->conflict_marker_size;
@@ -3234,7 +3217,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
}
bad = ws_check(line + 1, len - 1, data->ws_rule);
if (!bad)
- return;
+ return 0;
data->status |= bad;
err = whitespace_error_string(bad);
fprintf(data->o->file, "%s%s:%d: %s.\n",
@@ -3246,6 +3229,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
} else if (line[0] == ' ') {
data->lineno++;
}
+ return 0;
}
static unsigned char *deflate_it(char *data,
@@ -3400,6 +3384,59 @@ struct userdiff_driver *get_textconv(struct repository *r,
return userdiff_get_textconv(r, one->driver);
}
+static struct string_list *additional_headers(struct diff_options *o,
+ const char *path)
+{
+ if (!o->additional_path_headers)
+ return NULL;
+ return strmap_get(o->additional_path_headers, path);
+}
+
+static void add_formatted_header(struct strbuf *msg,
+ const char *header,
+ const char *line_prefix,
+ const char *meta,
+ const char *reset)
+{
+ const char *next, *newline;
+
+ for (next = header; *next; next = newline) {
+ newline = strchrnul(next, '\n');
+ strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
+ (int)(newline - next), next, reset);
+ if (*newline)
+ newline++;
+ }
+}
+
+static void add_formatted_headers(struct strbuf *msg,
+ struct string_list *more_headers,
+ const char *line_prefix,
+ const char *meta,
+ const char *reset)
+{
+ int i;
+
+ for (i = 0; i < more_headers->nr; i++)
+ add_formatted_header(msg, more_headers->items[i].string,
+ line_prefix, meta, reset);
+}
+
+static int diff_filepair_is_phoney(struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ /*
+ * This function specifically looks for pairs injected by
+ * create_filepairs_for_header_only_notifications(). Such
+ * pairs are "phoney" in that they do not represent any
+ * content or even mode difference, but were inserted because
+ * diff_queued_diff previously had no pair associated with
+ * that path but we needed some pair to avoid losing the
+ * "remerge CONFLICT" header associated with the path.
+ */
+ return !DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two);
+}
+
static void builtin_diff(const char *name_a,
const char *name_b,
struct diff_filespec *one,
@@ -3431,14 +3468,16 @@ static void builtin_diff(const char *name_a,
if (o->submodule_format == DIFF_SUBMODULE_LOG &&
(!one->mode || S_ISGITLINK(one->mode)) &&
- (!two->mode || S_ISGITLINK(two->mode))) {
+ (!two->mode || S_ISGITLINK(two->mode)) &&
+ (!diff_filepair_is_phoney(one, two))) {
show_submodule_diff_summary(o, one->path ? one->path : two->path,
&one->oid, &two->oid,
two->dirty_submodule);
return;
} else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
(!one->mode || S_ISGITLINK(one->mode)) &&
- (!two->mode || S_ISGITLINK(two->mode))) {
+ (!two->mode || S_ISGITLINK(two->mode)) &&
+ (!diff_filepair_is_phoney(one, two))) {
show_submodule_inline_diff(o, one->path ? one->path : two->path,
&one->oid, &two->oid,
two->dirty_submodule);
@@ -3458,6 +3497,17 @@ static void builtin_diff(const char *name_a,
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+ if (diff_filepair_is_phoney(one, two)) {
+ /*
+ * We should only reach this point for pairs generated from
+ * create_filepairs_for_header_only_notifications(). For
+ * these, we want to avoid the "/dev/null" special casing
+ * above, because we do not want such pairs shown as either
+ * "new file" or "deleted file" below.
+ */
+ lbl[0] = a_one;
+ lbl[1] = b_two;
+ }
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
if (lbl[0][0] == '/') {
/* /dev/null */
@@ -3724,7 +3774,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
xpp.anchors_nr = o->anchors_nr;
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
- if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line,
+ xecfg.flags = XDL_EMIT_NO_HUNK_HDR;
+ if (xdi_diff_outf(&mf1, &mf2, NULL,
diffstat_consume, diffstat, &xpp, &xecfg))
die("unable to generate diffstat for %s", one->path);
@@ -3901,6 +3952,13 @@ static int reuse_worktree_file(struct index_state *istate,
if (!want_file && would_convert_to_git(istate, name))
return 0;
+ /*
+ * If this path does not match our sparse-checkout definition,
+ * then the file will not be in the working directory.
+ */
+ if (!path_in_sparse_checkout(name, istate))
+ return 0;
+
len = strlen(name);
pos = index_name_pos(istate, name, len);
if (pos < 0)
@@ -4130,18 +4188,13 @@ static void prep_temp_blob(struct index_state *istate,
int mode)
{
struct strbuf buf = STRBUF_INIT;
- struct strbuf tempfile = STRBUF_INIT;
char *path_dup = xstrdup(path);
const char *base = basename(path_dup);
struct checkout_metadata meta;
init_checkout_metadata(&meta, NULL, NULL, oid);
- /* Generate "XXXXXX_basename.ext" */
- strbuf_addstr(&tempfile, "XXXXXX_");
- strbuf_addstr(&tempfile, base);
-
- temp->tempfile = mks_tempfile_ts(tempfile.buf, strlen(base) + 1);
+ temp->tempfile = mks_tempfile_dt("git-blob-XXXXXX", base);
if (!temp->tempfile)
die_errno("unable to create temp-file");
if (convert_to_working_tree(istate, path,
@@ -4156,7 +4209,6 @@ static void prep_temp_blob(struct index_state *istate,
oid_to_hex_r(temp->hex, oid);
xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
strbuf_release(&buf);
- strbuf_release(&tempfile);
free(path_dup);
}
@@ -4192,7 +4244,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
die_errno("readlink(%s)", name);
prep_temp_blob(r->index, name, temp, sb.buf, sb.len,
(one->oid_valid ?
- &one->oid : &null_oid),
+ &one->oid : null_oid()),
(one->oid_valid ?
one->mode : S_IFLNK));
strbuf_release(&sb);
@@ -4201,7 +4253,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
/* we can borrow from the file in the work tree */
temp->name = name;
if (!one->oid_valid)
- oid_to_hex_r(temp->hex, &null_oid);
+ oid_to_hex_r(temp->hex, null_oid());
else
oid_to_hex_r(temp->hex, &one->oid);
/* Even though we may sometimes borrow the
@@ -4249,35 +4301,34 @@ static void run_external_diff(const char *pgm,
const char *xfrm_msg,
struct diff_options *o)
{
- struct strvec argv = STRVEC_INIT;
- struct strvec env = STRVEC_INIT;
+ struct child_process cmd = CHILD_PROCESS_INIT;
struct diff_queue_struct *q = &diff_queued_diff;
- strvec_push(&argv, pgm);
- strvec_push(&argv, name);
+ strvec_push(&cmd.args, pgm);
+ strvec_push(&cmd.args, name);
if (one && two) {
- add_external_diff_name(o->repo, &argv, name, one);
+ add_external_diff_name(o->repo, &cmd.args, name, one);
if (!other)
- add_external_diff_name(o->repo, &argv, name, two);
+ add_external_diff_name(o->repo, &cmd.args, name, two);
else {
- add_external_diff_name(o->repo, &argv, other, two);
- strvec_push(&argv, other);
- strvec_push(&argv, xfrm_msg);
+ add_external_diff_name(o->repo, &cmd.args, other, two);
+ strvec_push(&cmd.args, other);
+ strvec_push(&cmd.args, xfrm_msg);
}
}
- strvec_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
- strvec_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
+ strvec_pushf(&cmd.env, "GIT_DIFF_PATH_COUNTER=%d",
+ ++o->diff_path_counter);
+ strvec_pushf(&cmd.env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
diff_free_filespec_data(one);
diff_free_filespec_data(two);
- if (run_command_v_opt_cd_env(argv.v, RUN_USING_SHELL, NULL, env.v))
+ cmd.use_shell = 1;
+ if (run_command(&cmd))
die(_("external diff died, stopping at %s"), name);
remove_tempfile();
- strvec_clear(&argv);
- strvec_clear(&env);
}
static int similarity_index(struct diff_filepair *p)
@@ -4314,6 +4365,7 @@ static void fill_metainfo(struct strbuf *msg,
const char *set = diff_get_color(use_color, DIFF_METAINFO);
const char *reset = diff_get_color(use_color, DIFF_RESET);
const char *line_prefix = diff_line_prefix(o);
+ struct string_list *more_headers = NULL;
*must_show_header = 1;
strbuf_init(msg, PATH_MAX * 2 + 300);
@@ -4350,6 +4402,11 @@ static void fill_metainfo(struct strbuf *msg,
default:
*must_show_header = 0;
}
+ if ((more_headers = additional_headers(o, name))) {
+ add_formatted_headers(msg, more_headers,
+ line_prefix, set, reset);
+ *must_show_header = 1;
+ }
if (one && two && !oideq(&one->oid, &two->oid)) {
const unsigned hexsz = the_hash_algo->hexsz;
int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
@@ -4593,6 +4650,9 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
options->orderfile = diff_order_file_cfg;
+ if (!options->flags.ignore_submodule_set)
+ options->flags.ignore_untracked_in_submodules = 1;
+
if (diff_no_prefix) {
options->a_prefix = options->b_prefix = "";
} else if (!diff_mnemonic_prefix) {
@@ -4606,6 +4666,43 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
prep_parse_options(options);
}
+static const char diff_status_letters[] = {
+ DIFF_STATUS_ADDED,
+ DIFF_STATUS_COPIED,
+ DIFF_STATUS_DELETED,
+ DIFF_STATUS_MODIFIED,
+ DIFF_STATUS_RENAMED,
+ DIFF_STATUS_TYPE_CHANGED,
+ DIFF_STATUS_UNKNOWN,
+ DIFF_STATUS_UNMERGED,
+ DIFF_STATUS_FILTER_AON,
+ DIFF_STATUS_FILTER_BROKEN,
+ '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+ int i;
+
+ if (!filter_bit[DIFF_STATUS_ADDED]) {
+ for (i = 0; diff_status_letters[i]; i++)
+ filter_bit[(int) diff_status_letters[i]] = (1 << i);
+ }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+ return opt->filter & filter_bit[(int) status];
+}
+
+unsigned diff_filter_bit(char status)
+{
+ prepare_filter_bits();
+ return filter_bit[(int) status];
+}
+
void diff_setup_done(struct diff_options *options)
{
unsigned check_mask = DIFF_FORMAT_NAME |
@@ -4622,10 +4719,20 @@ void diff_setup_done(struct diff_options *options)
options->set_default(options);
if (HAS_MULTI_BITS(options->output_format & check_mask))
- die(_("--name-only, --name-status, --check and -s are mutually exclusive"));
+ die(_("options '%s', '%s', '%s', and '%s' cannot be used together"),
+ "--name-only", "--name-status", "--check", "-s");
if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
- die(_("-G, -S and --find-object are mutually exclusive"));
+ die(_("options '%s', '%s', and '%s' cannot be used together"),
+ "-G", "-S", "--find-object");
+
+ if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_G_REGEX_MASK))
+ die(_("options '%s' and '%s' cannot be used together, use '%s' with '%s'"),
+ "-G", "--pickaxe-regex", "--pickaxe-regex", "-S");
+
+ if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_ALL_OBJFIND_MASK))
+ die(_("options '%s' and '%s' cannot be used together, use '%s' with '%s' and '%s'"),
+ "--pickaxe-all", "--find-object", "--pickaxe-all", "-G", "-S");
/*
* Most of the time we can say "there are changes"
@@ -4709,6 +4816,12 @@ void diff_setup_done(struct diff_options *options)
if (!options->use_color || external_diff())
options->color_moved = 0;
+ if (options->filter_not) {
+ if (!options->filter)
+ options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON];
+ options->filter &= ~options->filter_not;
+ }
+
FREE_AND_NULL(options->parseopts);
}
@@ -4800,43 +4913,6 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
return 1;
}
-static const char diff_status_letters[] = {
- DIFF_STATUS_ADDED,
- DIFF_STATUS_COPIED,
- DIFF_STATUS_DELETED,
- DIFF_STATUS_MODIFIED,
- DIFF_STATUS_RENAMED,
- DIFF_STATUS_TYPE_CHANGED,
- DIFF_STATUS_UNKNOWN,
- DIFF_STATUS_UNMERGED,
- DIFF_STATUS_FILTER_AON,
- DIFF_STATUS_FILTER_BROKEN,
- '\0',
-};
-
-static unsigned int filter_bit['Z' + 1];
-
-static void prepare_filter_bits(void)
-{
- int i;
-
- if (!filter_bit[DIFF_STATUS_ADDED]) {
- for (i = 0; diff_status_letters[i]; i++)
- filter_bit[(int) diff_status_letters[i]] = (1 << i);
- }
-}
-
-static unsigned filter_bit_tst(char status, const struct diff_options *opt)
-{
- return opt->filter & filter_bit[(int) status];
-}
-
-unsigned diff_filter_bit(char status)
-{
- prepare_filter_bits();
- return filter_bit[(int) status];
-}
-
static int diff_opt_diff_filter(const struct option *option,
const char *optarg, int unset)
{
@@ -4846,21 +4922,6 @@ static int diff_opt_diff_filter(const struct option *option,
BUG_ON_OPT_NEG(unset);
prepare_filter_bits();
- /*
- * If there is a negation e.g. 'd' in the input, and we haven't
- * initialized the filter field with another --diff-filter, start
- * from full set of bits, except for AON.
- */
- if (!opt->filter) {
- for (i = 0; (optch = optarg[i]) != '\0'; i++) {
- if (optch < 'a' || 'z' < optch)
- continue;
- opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
- opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
- break;
- }
- }
-
for (i = 0; (optch = optarg[i]) != '\0'; i++) {
unsigned int bit;
int negate;
@@ -4877,7 +4938,7 @@ static int diff_opt_diff_filter(const struct option *option,
return error(_("unknown change class '%c' in --diff-filter=%s"),
optarg[i], optarg);
if (negate)
- opt->filter &= ~bit;
+ opt->filter_not |= bit;
else
opt->filter |= bit;
}
@@ -4915,7 +4976,7 @@ static int diff_opt_find_object(const struct option *option,
return error(_("unable to resolve '%s'"), arg);
if (!opt->objfind)
- opt->objfind = xcalloc(1, sizeof(*opt->objfind));
+ CALLOC_ARRAY(opt->objfind, 1);
opt->pickaxe_opts |= DIFF_PICKAXE_KIND_OBJFIND;
opt->flags.recursive = 1;
@@ -5345,6 +5406,19 @@ static int diff_opt_word_diff_regex(const struct option *opt,
return 0;
}
+static int diff_opt_rotate_to(const struct option *opt, const char *arg, int unset)
+{
+ struct diff_options *options = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+ if (!strcmp(opt->long_name, "skip-to"))
+ options->skip_instead_of_rotate = 1;
+ else
+ options->skip_instead_of_rotate = 0;
+ options->rotate_to = arg;
+ return 0;
+}
+
static void prep_parse_options(struct diff_options *options)
{
struct option parseopts[] = {
@@ -5596,6 +5670,12 @@ static void prep_parse_options(struct diff_options *options)
DIFF_PICKAXE_REGEX, PARSE_OPT_NONEG),
OPT_FILENAME('O', NULL, &options->orderfile,
N_("control the order in which files appear in the output")),
+ OPT_CALLBACK_F(0, "rotate-to", options, N_("<path>"),
+ N_("show the change in the specified path first"),
+ PARSE_OPT_NONEG, diff_opt_rotate_to),
+ OPT_CALLBACK_F(0, "skip-to", options, N_("<path>"),
+ N_("skip the output to the specified path"),
+ PARSE_OPT_NONEG, diff_opt_rotate_to),
OPT_CALLBACK_F(0, "find-object", options, N_("<object-id>"),
N_("look for differences that change the number of occurrences of the specified object"),
PARSE_OPT_NONEG, diff_opt_find_object),
@@ -5603,7 +5683,7 @@ static void prep_parse_options(struct diff_options *options)
N_("select files by diff type"),
PARSE_OPT_NONEG, diff_opt_diff_filter),
{ OPTION_CALLBACK, 0, "output", options, N_("<file>"),
- N_("Output to a specific file"),
+ N_("output to a specific file"),
PARSE_OPT_NONEG, NULL, 0, diff_opt_output },
OPT_END()
@@ -5621,7 +5701,7 @@ int diff_opt_parse(struct diff_options *options,
ac = parse_options(ac, av, prefix, options->parseopts, NULL,
PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_NO_INTERNAL_HELP |
PARSE_OPT_ONE_SHOT |
PARSE_OPT_STOP_AT_NON_OPTION);
@@ -5692,6 +5772,13 @@ void diff_free_filepair(struct diff_filepair *p)
free(p);
}
+void diff_free_queue(struct diff_queue_struct *q)
+{
+ for (int i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ free(q->queue);
+}
+
const char *diff_aligned_abbrev(const struct object_id *oid, int len)
{
int abblen;
@@ -5810,12 +5897,28 @@ int diff_unmodified_pair(struct diff_filepair *p)
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
{
- if (diff_unmodified_pair(p))
+ int include_conflict_headers =
+ (additional_headers(o, p->one->path) &&
+ !o->pickaxe_opts &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ /*
+ * Check if we can return early without showing a diff. Note that
+ * diff_filepair only stores {oid, path, mode, is_valid}
+ * information for each path, and thus diff_unmodified_pair() only
+ * considers those bits of info. However, we do not want pairs
+ * created by create_filepairs_for_header_only_notifications()
+ * (which always look like unmodified pairs) to be ignored, so
+ * return early if both p is unmodified AND we don't want to
+ * include_conflict_headers.
+ */
+ if (diff_unmodified_pair(p) && !include_conflict_headers)
return;
+ /* Actually, we can also return early to avoid showing tree diffs */
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return;
run_diff(p, o);
}
@@ -5846,10 +5949,19 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
run_checkdiff(p, o);
}
-int diff_queue_is_empty(void)
+int diff_queue_is_empty(struct diff_options *o)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
+ int include_conflict_headers =
+ (o->additional_path_headers &&
+ strmap_get_size(o->additional_path_headers) &&
+ !o->pickaxe_opts &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ if (include_conflict_headers)
+ return 0;
+
for (i = 0; i < q->nr; i++)
if (!diff_unmodified_pair(q->queue[i]))
return 0;
@@ -6095,17 +6207,18 @@ void flush_one_hunk(struct object_id *result, git_hash_ctx *ctx)
}
}
-static void patch_id_consume(void *priv, char *line, unsigned long len)
+static int patch_id_consume(void *priv, char *line, unsigned long len)
{
struct patch_id_t *data = priv;
int new_len;
if (len > 12 && starts_with(line, "\\ "))
- return;
+ return 0;
new_len = remove_space(line, len);
the_hash_algo->update_fn(data->ctx, line, new_len);
data->patchlen += new_len;
+ return 0;
}
static void patch_id_add_string(git_hash_ctx *ctx, const char *str)
@@ -6122,7 +6235,7 @@ static void patch_id_add_mode(git_hash_ctx *ctx, unsigned mode)
}
/* returns 0 upon success, and writes result into oid */
-static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable)
+static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
@@ -6169,66 +6282,63 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid
if (p->one->mode == 0) {
patch_id_add_string(&ctx, "newfilemode");
patch_id_add_mode(&ctx, p->two->mode);
- patch_id_add_string(&ctx, "---/dev/null");
- patch_id_add_string(&ctx, "+++b/");
- the_hash_algo->update_fn(&ctx, p->two->path, len2);
} else if (p->two->mode == 0) {
patch_id_add_string(&ctx, "deletedfilemode");
patch_id_add_mode(&ctx, p->one->mode);
- patch_id_add_string(&ctx, "---a/");
- the_hash_algo->update_fn(&ctx, p->one->path, len1);
- patch_id_add_string(&ctx, "+++/dev/null");
- } else {
- patch_id_add_string(&ctx, "---a/");
- the_hash_algo->update_fn(&ctx, p->one->path, len1);
- patch_id_add_string(&ctx, "+++b/");
- the_hash_algo->update_fn(&ctx, p->two->path, len2);
+ } else if (p->one->mode != p->two->mode) {
+ patch_id_add_string(&ctx, "oldmode");
+ patch_id_add_mode(&ctx, p->one->mode);
+ patch_id_add_string(&ctx, "newmode");
+ patch_id_add_mode(&ctx, p->two->mode);
}
- if (diff_header_only)
- continue;
-
- if (fill_mmfile(options->repo, &mf1, p->one) < 0 ||
- fill_mmfile(options->repo, &mf2, p->two) < 0)
- return error("unable to read files to diff");
-
- if (diff_filespec_is_binary(options->repo, p->one) ||
+ if (diff_header_only) {
+ /* don't do anything since we're only populating header info */
+ } else if (diff_filespec_is_binary(options->repo, p->one) ||
diff_filespec_is_binary(options->repo, p->two)) {
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->one->oid),
the_hash_algo->hexsz);
the_hash_algo->update_fn(&ctx, oid_to_hex(&p->two->oid),
the_hash_algo->hexsz);
- continue;
- }
-
- xpp.flags = 0;
- xecfg.ctxlen = 3;
- xecfg.flags = 0;
- if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line,
- patch_id_consume, &data, &xpp, &xecfg))
- return error("unable to generate patch-id diff for %s",
- p->one->path);
+ } else {
+ if (p->one->mode == 0) {
+ patch_id_add_string(&ctx, "---/dev/null");
+ patch_id_add_string(&ctx, "+++b/");
+ the_hash_algo->update_fn(&ctx, p->two->path, len2);
+ } else if (p->two->mode == 0) {
+ patch_id_add_string(&ctx, "---a/");
+ the_hash_algo->update_fn(&ctx, p->one->path, len1);
+ patch_id_add_string(&ctx, "+++/dev/null");
+ } else {
+ patch_id_add_string(&ctx, "---a/");
+ the_hash_algo->update_fn(&ctx, p->one->path, len1);
+ patch_id_add_string(&ctx, "+++b/");
+ the_hash_algo->update_fn(&ctx, p->two->path, len2);
+ }
- if (stable)
- flush_one_hunk(oid, &ctx);
+ if (fill_mmfile(options->repo, &mf1, p->one) < 0 ||
+ fill_mmfile(options->repo, &mf2, p->two) < 0)
+ return error("unable to read files to diff");
+ xpp.flags = 0;
+ xecfg.ctxlen = 3;
+ xecfg.flags = XDL_EMIT_NO_HUNK_HDR;
+ if (xdi_diff_outf(&mf1, &mf2, NULL,
+ patch_id_consume, &data, &xpp, &xecfg))
+ return error("unable to generate patch-id diff for %s",
+ p->one->path);
+ }
+ flush_one_hunk(oid, &ctx);
}
- if (!stable)
- the_hash_algo->final_fn(oid->hash, &ctx);
-
return 0;
}
-int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable)
+int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
{
struct diff_queue_struct *q = &diff_queued_diff;
- int i;
- int result = diff_get_patch_id(options, oid, diff_header_only, stable);
+ int result = diff_get_patch_id(options, oid, diff_header_only);
- for (i = 0; i < q->nr; i++)
- diff_free_filepair(q->queue[i]);
-
- free(q->queue);
+ diff_free_queue(q);
DIFF_QUEUE_CLEAR(q);
return result;
@@ -6260,7 +6370,7 @@ static int is_summary_empty(const struct diff_queue_struct *q)
}
static const char rename_limit_warning[] =
-N_("inexact rename detection was skipped due to too many files.");
+N_("exhaustive rename detection was skipped due to too many files.");
static const char degrade_cc_to_c_warning[] =
N_("only found copies from modified paths due to too many files.");
@@ -6282,6 +6392,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
warning(_(rename_limit_advice), varname, needed);
}
+static void create_filepairs_for_header_only_notifications(struct diff_options *o)
+{
+ struct strset present;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct hashmap_iter iter;
+ struct strmap_entry *e;
+ int i;
+
+ strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
+
+ /*
+ * Find out which paths exist in diff_queued_diff, preferring
+ * one->path for any pair that has multiple paths.
+ */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ char *path = p->one->path ? p->one->path : p->two->path;
+
+ if (strmap_contains(o->additional_path_headers, path))
+ strset_add(&present, path);
+ }
+
+ /*
+ * Loop over paths in additional_path_headers; for each NOT already
+ * in diff_queued_diff, create a synthetic filepair and insert that
+ * into diff_queued_diff.
+ */
+ strmap_for_each_entry(o->additional_path_headers, &iter, e) {
+ if (!strset_contains(&present, e->key)) {
+ struct diff_filespec *one, *two;
+ struct diff_filepair *p;
+
+ one = alloc_filespec(e->key);
+ two = alloc_filespec(e->key);
+ fill_filespec(one, null_oid(), 0, 0);
+ fill_filespec(two, null_oid(), 0, 0);
+ p = diff_queue(q, one, two);
+ p->status = DIFF_STATUS_MODIFIED;
+ }
+ }
+
+ /* Re-sort the filepairs */
+ diffcore_fix_diff_index();
+
+ /* Cleanup */
+ strset_clear(&present);
+}
+
static void diff_flush_patch_all_file_pairs(struct diff_options *o)
{
int i;
@@ -6294,6 +6452,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
if (o->color_moved)
o->emitted_symbols = &esm;
+ if (o->additional_path_headers)
+ create_filepairs_for_header_only_notifications(o);
+
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
if (check_pair_status(p))
@@ -6302,24 +6463,18 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
if (o->emitted_symbols) {
if (o->color_moved) {
- struct hashmap add_lines, del_lines;
-
- if (o->color_moved_ws_handling &
- COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
- o->color_moved_ws_handling |= XDF_IGNORE_WHITESPACE;
-
- hashmap_init(&del_lines, moved_entry_cmp, o, 0);
- hashmap_init(&add_lines, moved_entry_cmp, o, 0);
+ struct mem_pool entry_pool;
+ struct moved_entry_list *entry_list;
- add_lines_to_move_detection(o, &add_lines, &del_lines);
- mark_color_as_moved(o, &add_lines, &del_lines);
+ mem_pool_init(&entry_pool, 1024 * 1024);
+ entry_list = add_lines_to_move_detection(o,
+ &entry_pool);
+ mark_color_as_moved(o, entry_list);
if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
dim_moved_lines(o);
- hashmap_clear_and_free(&add_lines, struct moved_entry,
- ent);
- hashmap_clear_and_free(&del_lines, struct moved_entry,
- ent);
+ mem_pool_discard(&entry_pool, 0);
+ free(entry_list);
}
for (i = 0; i < esm.nr; i++)
@@ -6333,6 +6488,34 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
}
}
+static void diff_free_file(struct diff_options *options)
+{
+ if (options->close_file)
+ fclose(options->file);
+}
+
+static void diff_free_ignore_regex(struct diff_options *options)
+{
+ int i;
+
+ for (i = 0; i < options->ignore_regex_nr; i++) {
+ regfree(options->ignore_regex[i]);
+ free(options->ignore_regex[i]);
+ }
+ free(options->ignore_regex);
+}
+
+void diff_free(struct diff_options *options)
+{
+ if (options->no_free)
+ return;
+
+ diff_free_file(options);
+ diff_free_ignore_regex(options);
+ clear_pathspec(&options->pathspec);
+ FREE_AND_NULL(options->parseopts);
+}
+
void diff_flush(struct diff_options *options)
{
struct diff_queue_struct *q = &diff_queued_diff;
@@ -6344,7 +6527,7 @@ void diff_flush(struct diff_options *options)
* Order: raw, stat, summary, patch
* or: name/name-status/checkdiff (other bits clear)
*/
- if (!q->nr)
+ if (!q->nr && !options->additional_path_headers)
goto free_queue;
if (output_format & (DIFF_FORMAT_RAW |
@@ -6396,8 +6579,7 @@ void diff_flush(struct diff_options *options)
* options->file to /dev/null should be safe, because we
* aren't supposed to produce any output anyway.
*/
- if (options->close_file)
- fclose(options->file);
+ diff_free_file(options);
options->file = xfopen("/dev/null", "w");
options->close_file = 1;
options->color_moved = 0;
@@ -6425,13 +6607,10 @@ void diff_flush(struct diff_options *options)
if (output_format & DIFF_FORMAT_CALLBACK)
options->format_callback(q, options, options->format_callback_data);
- for (i = 0; i < q->nr; i++)
- diff_free_filepair(q->queue[i]);
free_queue:
- free(q->queue);
+ diff_free_queue(q);
DIFF_QUEUE_CLEAR(q);
- if (options->close_file)
- fclose(options->file);
+ diff_free(options);
/*
* Report the content-level differences with HAS_CHANGES;
@@ -6666,6 +6845,8 @@ void diffcore_std(struct diff_options *options)
diffcore_pickaxe(options);
if (options->orderfile)
diffcore_order(options->orderfile);
+ if (options->rotate_to)
+ diffcore_rotate(options);
if (!options->found_follow)
/* See try_to_follow_renames() in tree-diff.c */
diff_resolve_rename_copy();
@@ -6852,19 +7033,15 @@ static char *run_textconv(struct repository *r,
size_t *outsize)
{
struct diff_tempfile *temp;
- const char *argv[3];
- const char **arg = argv;
struct child_process child = CHILD_PROCESS_INIT;
struct strbuf buf = STRBUF_INIT;
int err = 0;
temp = prepare_temp_file(r, spec->path, spec);
- *arg++ = pgm;
- *arg++ = temp->name;
- *arg = NULL;
+ strvec_push(&child.args, pgm);
+ strvec_push(&child.args, temp->name);
child.use_shell = 1;
- child.argv = argv;
child.out = -1;
if (start_command(&child)) {
remove_tempfile();