aboutsummaryrefslogtreecommitdiff
path: root/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'diff.c')
-rw-r--r--diff.c511
1 files changed, 437 insertions, 74 deletions
diff --git a/diff.c b/diff.c
index a1961526c0..397e38b41c 100644
--- a/diff.c
+++ b/diff.c
@@ -601,12 +601,59 @@ struct emit_callback {
int blank_at_eof_in_postimage;
int lno_in_preimage;
int lno_in_postimage;
+ int last_line_kind;
const char **label_path;
struct diff_words_data *diff_words;
struct diff_options *opt;
struct strbuf *header;
};
+/*
+ * State for the line-range callback wrappers that sit between
+ * xdi_diff_outf() and fn_out_consume(). xdiff produces a normal,
+ * unfiltered diff; the wrappers intercept each hunk header and line,
+ * track post-image position, and forward only lines that fall within
+ * the requested ranges. Contiguous in-range lines are collected into
+ * range hunks and flushed with a synthetic @@ header so that
+ * fn_out_consume() sees well-formed unified-diff fragments.
+ *
+ * Removal lines ('-') cannot be classified by post-image position, so
+ * they are buffered in pending_rm until the next '+' or ' ' line
+ * reveals whether they precede an in-range line (flush into range hunk) or
+ * an out-of-range line (discard).
+ */
+struct line_range_callback {
+ xdiff_emit_line_fn orig_line_fn;
+ void *orig_cb_data;
+ const struct range_set *ranges; /* 0-based [start, end) */
+ unsigned int cur_range; /* index into the range_set */
+
+ /* Post/pre-image line counters (1-based, set from hunk headers) */
+ long lno_post;
+ long lno_pre;
+
+ /*
+ * Function name from most recent xdiff hunk header;
+ * size matches struct func_line.buf in xdiff/xemit.c.
+ */
+ char func[80];
+ long funclen;
+
+ /* Range hunk being accumulated for the current range */
+ struct strbuf rhunk;
+ long rhunk_old_begin, rhunk_old_count;
+ long rhunk_new_begin, rhunk_new_count;
+ int rhunk_active;
+ int rhunk_has_changes; /* any '+' or '-' lines? */
+
+ /* Removal lines not yet known to be in-range */
+ struct strbuf pending_rm;
+ int pending_rm_count;
+ long pending_rm_pre_begin; /* pre-image line of first pending */
+
+ int ret; /* latched error from orig_line_fn */
+};
+
static int count_lines(const char *data, int size)
{
int count, ch, completely_empty = 1, nl_just_seen = 0;
@@ -796,21 +843,23 @@ enum diff_symbol {
DIFF_SYMBOL_CONTEXT_INCOMPLETE,
DIFF_SYMBOL_PLUS,
DIFF_SYMBOL_MINUS,
- DIFF_SYMBOL_NO_LF_EOF,
DIFF_SYMBOL_CONTEXT_FRAGINFO,
DIFF_SYMBOL_CONTEXT_MARKER,
DIFF_SYMBOL_SEPARATOR
};
+
/*
* Flags for content lines:
- * 0..12 are whitespace rules
- * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
- * 16 is marking if the line is blank at EOF
+ * 0..15 are whitespace rules (see ws.h)
+ * 16..18 are WSEH_NEW | WSEH_CONTEXT | WSEH_OLD
+ * 19 is marking if the line is blank at EOF
+ * 20..22 are used for color-moved.
*/
-#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16)
-#define DIFF_SYMBOL_MOVED_LINE (1<<17)
-#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18)
-#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19)
+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<19)
+#define DIFF_SYMBOL_MOVED_LINE (1<<20)
+#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<21)
+#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<22)
+
#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
/*
@@ -1318,20 +1367,25 @@ static void emit_line_ws_markup(struct diff_options *o,
const char *ws = NULL;
int sign = o->output_indicators[sign_index];
+ if (diff_suppress_blank_empty &&
+ sign_index == OUTPUT_INDICATOR_CONTEXT &&
+ len == 1 && line[0] == '\n')
+ sign = 0;
+
if (o->ws_error_highlight & ws_rule) {
ws = diff_get_color_opt(o, DIFF_WHITESPACE);
if (!*ws)
ws = NULL;
}
- if (!ws && !set_sign)
+ if (!ws && !set_sign) {
emit_line_0(o, set, NULL, 0, reset, sign, line, len);
- else if (!ws) {
+ } else if (!ws) {
emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len);
- } else if (blank_at_eof)
+ } else if (blank_at_eof) {
/* Blank line at EOF - paint '+' as well */
emit_line_0(o, ws, NULL, 0, reset, sign, line, len);
- else {
+ } else {
/* Emit just the prefix, then the rest. */
emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset,
sign, "", 0);
@@ -1343,7 +1397,6 @@ static void emit_line_ws_markup(struct diff_options *o,
static void emit_diff_symbol_from_struct(struct diff_options *o,
struct emitted_diff_symbol *eds)
{
- static const char *nneof = " No newline at end of file\n";
const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
enum diff_symbol s = eds->s;
@@ -1355,13 +1408,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
return;
switch (s) {
- case DIFF_SYMBOL_NO_LF_EOF:
- context = diff_get_color_opt(o, DIFF_CONTEXT);
- reset = diff_get_color_opt(o, DIFF_RESET);
- putc('\n', o->file);
- emit_line_0(o, context, NULL, 0, reset, '\\',
- nneof, strlen(nneof));
- break;
case DIFF_SYMBOL_SUBMODULE_HEADER:
case DIFF_SYMBOL_SUBMODULE_ERROR:
case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH:
@@ -1373,6 +1419,14 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
emit_line(o, "", "", line, len);
break;
case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
+ if ((flags & WS_INCOMPLETE_LINE) &&
+ (flags & o->ws_error_highlight))
+ set = diff_get_color_opt(o, DIFF_WHITESPACE);
+ else
+ set = diff_get_color_opt(o, DIFF_CONTEXT);
+ reset = diff_get_color_opt(o, DIFF_RESET);
+ emit_line(o, set, reset, line, len);
+ break;
case DIFF_SYMBOL_CONTEXT_MARKER:
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
@@ -1498,15 +1552,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
case DIFF_SYMBOL_WORDS:
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
- /*
- * Skip the prefix character, if any. With
- * diff_suppress_blank_empty, there may be
- * none.
- */
- if (line[0] != '\n') {
- line++;
- len--;
- }
+
+ /* Skip the prefix character */
+ line++; len--;
emit_line(o, context, reset, line, len);
break;
case DIFF_SYMBOL_FILEPAIR_PLUS:
@@ -1668,6 +1716,19 @@ static void emit_context_line(struct emit_callback *ecbdata,
emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags);
}
+static void emit_incomplete_line_marker(struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ int last_line_kind = ecbdata->last_line_kind;
+ unsigned flags = (last_line_kind == '+'
+ ? WSEH_NEW
+ : last_line_kind == '-'
+ ? WSEH_OLD
+ : WSEH_CONTEXT) | ecbdata->ws_rule;
+ emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
+ line, len, flags);
+}
+
static void emit_hunk_header(struct emit_callback *ecbdata,
const char *line, int len)
{
@@ -1769,28 +1830,44 @@ static void add_line_count(struct strbuf *out, int count)
}
}
-static void emit_rewrite_lines(struct emit_callback *ecb,
+static void emit_rewrite_lines(struct emit_callback *ecbdata,
int prefix, const char *data, int size)
{
const char *endp = NULL;
while (0 < size) {
- int len;
+ int len, plen;
+ char *pdata = NULL;
endp = memchr(data, '\n', size);
- len = endp ? (endp - data + 1) : size;
+
+ if (endp) {
+ len = endp - data + 1;
+ plen = len;
+ } else {
+ len = size;
+ plen = len + 1;
+ pdata = xmalloc(plen + 2);
+ memcpy(pdata, data, len);
+ pdata[len] = '\n';
+ pdata[len + 1] = '\0';
+ }
if (prefix != '+') {
- ecb->lno_in_preimage++;
- emit_del_line(ecb, data, len);
+ ecbdata->lno_in_preimage++;
+ emit_del_line(ecbdata, pdata ? pdata : data, plen);
} else {
- ecb->lno_in_postimage++;
- emit_add_line(ecb, data, len);
+ ecbdata->lno_in_postimage++;
+ emit_add_line(ecbdata, pdata ? pdata : data, plen);
}
+ free(pdata);
size -= len;
data += len;
}
- if (!endp)
- emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0);
+ if (!endp) {
+ static const char nneof[] = "\\ No newline at end of file\n";
+ ecbdata->last_line_kind = prefix;
+ emit_incomplete_line_marker(ecbdata, nneof, sizeof(nneof) - 1);
+ }
}
static void emit_rewrite_diff(const char *name_a,
@@ -1806,6 +1883,7 @@ static void emit_rewrite_diff(const char *name_a,
const char *a_prefix, *b_prefix;
char *data_one, *data_two;
size_t size_one, size_two;
+ unsigned ws_rule;
struct emit_callback ecbdata;
struct strbuf out = STRBUF_INIT;
@@ -1828,9 +1906,15 @@ static void emit_rewrite_diff(const char *name_a,
size_one = fill_textconv(o->repo, textconv_one, one, &data_one);
size_two = fill_textconv(o->repo, textconv_two, two, &data_two);
+ ws_rule = whitespace_rule(o->repo->index, name_b);
+
+ /* symlink being an incomplete line is not a news */
+ if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+ ws_rule &= ~WS_INCOMPLETE_LINE;
+
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.color_diff = o->use_color;
- ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
+ ecbdata.ws_rule = ws_rule;
ecbdata.opt = o;
if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
mmfile_t mf1, mf2;
@@ -1930,7 +2014,7 @@ static int fn_out_diff_words_write_helper(struct diff_options *o,
struct strbuf sb = STRBUF_INIT;
while (count) {
- char *p = memchr(buf, '\n', count);
+ const char *p = memchr(buf, '\n', count);
if (print)
strbuf_addstr(&sb, diff_line_prefix(o));
@@ -2375,12 +2459,6 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
- if (diff_suppress_blank_empty
- && len == 2 && line[0] == ' ' && line[1] == '\n') {
- line[0] = '\n';
- len = 1;
- }
-
if (line[0] == '@') {
if (ecbdata->diff_words)
diff_words_flush(ecbdata);
@@ -2431,13 +2509,24 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
ecbdata->lno_in_preimage++;
emit_context_line(ecbdata, line + 1, len - 1);
break;
- default:
+ case '\\':
/* incomplete line at the end */
+ switch (ecbdata->last_line_kind) {
+ case '+':
+ case '-':
+ case ' ':
+ break;
+ default:
+ BUG("fn_out_consume: '\\No newline' after unknown line (%c)",
+ ecbdata->last_line_kind);
+ }
ecbdata->lno_in_preimage++;
- emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
- line, len, 0);
+ emit_incomplete_line_marker(ecbdata, line, len);
break;
+ default:
+ BUG("fn_out_consume: unknown line '%s'", line);
}
+ ecbdata->last_line_kind = line[0];
return 0;
}
@@ -2450,6 +2539,188 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED
return 1;
}
+static void discard_pending_rm(struct line_range_callback *s)
+{
+ strbuf_reset(&s->pending_rm);
+ s->pending_rm_count = 0;
+}
+
+static void flush_rhunk(struct line_range_callback *s)
+{
+ struct strbuf hdr = STRBUF_INIT;
+ const char *p, *end;
+
+ if (!s->rhunk_active || s->ret)
+ return;
+
+ /* Drain any pending removal lines into the range hunk */
+ if (s->pending_rm_count) {
+ strbuf_addbuf(&s->rhunk, &s->pending_rm);
+ s->rhunk_old_count += s->pending_rm_count;
+ s->rhunk_has_changes = 1;
+ discard_pending_rm(s);
+ }
+
+ /*
+ * Suppress context-only hunks: they contain no actual changes
+ * and would just be noise. This can happen when the inflated
+ * ctxlen causes xdiff to emit context covering a range that
+ * has no changes in this commit.
+ */
+ if (!s->rhunk_has_changes) {
+ s->rhunk_active = 0;
+ strbuf_reset(&s->rhunk);
+ return;
+ }
+
+ strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@",
+ s->rhunk_old_begin, s->rhunk_old_count,
+ s->rhunk_new_begin, s->rhunk_new_count);
+ if (s->funclen > 0) {
+ strbuf_addch(&hdr, ' ');
+ strbuf_add(&hdr, s->func, s->funclen);
+ }
+ strbuf_addch(&hdr, '\n');
+
+ s->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);
+ strbuf_release(&hdr);
+
+ /*
+ * Replay buffered lines one at a time through fn_out_consume.
+ * The cast discards const because xdiff_emit_line_fn takes
+ * char *, though fn_out_consume does not modify the buffer.
+ */
+ p = s->rhunk.buf;
+ end = p + s->rhunk.len;
+ while (!s->ret && p < end) {
+ const char *eol = memchr(p, '\n', end - p);
+ unsigned long line_len = eol ? (unsigned long)(eol - p + 1)
+ : (unsigned long)(end - p);
+ s->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);
+ p += line_len;
+ }
+
+ s->rhunk_active = 0;
+ strbuf_reset(&s->rhunk);
+}
+
+static void line_range_hunk_fn(void *data,
+ long old_begin, long old_nr UNUSED,
+ long new_begin, long new_nr UNUSED,
+ const char *func, long funclen)
+{
+ struct line_range_callback *s = data;
+
+ /*
+ * When count > 0, begin is 1-based. When count == 0, begin is
+ * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
+ * that type will arrive, so the value is unused.
+ *
+ * Any pending removal lines from the previous xdiff hunk are
+ * intentionally left in pending_rm: the line callback will
+ * flush or discard them when the next content line reveals
+ * whether the removals precede in-range content.
+ */
+ s->lno_post = new_begin;
+ s->lno_pre = old_begin;
+
+ if (funclen > 0) {
+ if (funclen > (long)sizeof(s->func))
+ funclen = sizeof(s->func);
+ memcpy(s->func, func, funclen);
+ }
+ s->funclen = funclen;
+}
+
+static int line_range_line_fn(void *priv, char *line, unsigned long len)
+{
+ struct line_range_callback *s = priv;
+ const struct range *cur;
+ long lno_0, cur_pre;
+
+ if (s->ret)
+ return s->ret;
+
+ if (line[0] == '-') {
+ if (!s->pending_rm_count)
+ s->pending_rm_pre_begin = s->lno_pre;
+ s->lno_pre++;
+ strbuf_add(&s->pending_rm, line, len);
+ s->pending_rm_count++;
+ return s->ret;
+ }
+
+ if (line[0] == '\\') {
+ if (s->pending_rm_count)
+ strbuf_add(&s->pending_rm, line, len);
+ else if (s->rhunk_active)
+ strbuf_add(&s->rhunk, line, len);
+ /* otherwise outside tracked range; drop silently */
+ return s->ret;
+ }
+
+ if (line[0] != '+' && line[0] != ' ')
+ BUG("unexpected diff line type '%c'", line[0]);
+
+ lno_0 = s->lno_post - 1;
+ cur_pre = s->lno_pre; /* save before advancing for context lines */
+ s->lno_post++;
+ if (line[0] == ' ')
+ s->lno_pre++;
+
+ /* Advance past ranges we've passed */
+ while (s->cur_range < s->ranges->nr &&
+ lno_0 >= s->ranges->ranges[s->cur_range].end) {
+ if (s->rhunk_active)
+ flush_rhunk(s);
+ discard_pending_rm(s);
+ s->cur_range++;
+ }
+
+ /* Past all ranges */
+ if (s->cur_range >= s->ranges->nr) {
+ discard_pending_rm(s);
+ return s->ret;
+ }
+
+ cur = &s->ranges->ranges[s->cur_range];
+
+ /* Before current range */
+ if (lno_0 < cur->start) {
+ discard_pending_rm(s);
+ return s->ret;
+ }
+
+ /* In range so start a new range hunk if needed */
+ if (!s->rhunk_active) {
+ s->rhunk_active = 1;
+ s->rhunk_has_changes = 0;
+ s->rhunk_new_begin = lno_0 + 1;
+ s->rhunk_old_begin = s->pending_rm_count
+ ? s->pending_rm_pre_begin : cur_pre;
+ s->rhunk_old_count = 0;
+ s->rhunk_new_count = 0;
+ strbuf_reset(&s->rhunk);
+ }
+
+ /* Flush pending removals into range hunk */
+ if (s->pending_rm_count) {
+ strbuf_addbuf(&s->rhunk, &s->pending_rm);
+ s->rhunk_old_count += s->pending_rm_count;
+ s->rhunk_has_changes = 1;
+ discard_pending_rm(s);
+ }
+
+ strbuf_add(&s->rhunk, line, len);
+ s->rhunk_new_count++;
+ if (line[0] == '+')
+ s->rhunk_has_changes = 1;
+ else
+ s->rhunk_old_count++;
+
+ return s->ret;
+}
+
static void pprint_rename(struct strbuf *name, const char *a, const char *b)
{
const char *old_name = a;
@@ -2713,7 +2984,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
count = i; /* where we can stop scanning in data->files[] */
/*
- * We have width = stat_width or term_columns() columns total.
+ * We have width = stat_width or term_columns() columns total minus the
+ * length of line_prefix skipping ANSI escape codes to get the display
+ * width (e.g., skip ANSI-colored strings in "log --graph --stat").
* 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)
@@ -2740,14 +3013,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
* separators and this message, this message will "overflow"
* 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);
+ width = term_columns() - utf8_strnwidth(line_prefix, strlen(line_prefix), 1);
else
width = options->stat_width ? options->stat_width : 80;
number_width = decimal_width(max_change) > number_width ?
@@ -2823,17 +3090,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
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;
+ if (len < 0)
+ len = 0;
+
+ while (name_len > len)
+ name_len -= utf8_width((const char**)&name, NULL);
+
slash = strchr(name, '/');
if (slash)
name = slash;
@@ -3013,7 +3275,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
struct dirstat_file *f = dir->files;
int namelen = strlen(f->name);
unsigned long changes;
- char *slash;
+ const char *slash;
if (namelen < baselen)
break;
@@ -3231,6 +3493,7 @@ struct checkdiff_t {
struct diff_options *o;
unsigned ws_rule;
unsigned status;
+ int last_line_kind;
};
static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
@@ -3269,6 +3532,7 @@ static void checkdiff_consume_hunk(void *priv,
static int checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
+ int last_line_kind;
int marker_size = data->conflict_marker_size;
const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
@@ -3279,6 +3543,8 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
assert(data->o);
line_prefix = diff_line_prefix(data->o);
+ last_line_kind = data->last_line_kind;
+ data->last_line_kind = line[0];
if (line[0] == '+') {
unsigned bad;
data->lineno++;
@@ -3301,6 +3567,17 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
data->o->file, set, reset, ws);
} else if (line[0] == ' ') {
data->lineno++;
+ } else if (line[0] == '\\') {
+ /* no newline at the end of the line */
+ if ((data->ws_rule & WS_INCOMPLETE_LINE) &&
+ (last_line_kind == '+')) {
+ unsigned bad = WS_INCOMPLETE_LINE;
+ data->status |= bad;
+ err = whitespace_error_string(bad);
+ fprintf(data->o->file, "%s%s:%d: %s.\n",
+ line_prefix, data->filename, data->lineno, err);
+ free(err);
+ }
}
return 0;
}
@@ -3530,7 +3807,6 @@ static int set_diff_algorithm(struct diff_options *opts,
return -1;
/* clear out previous settings */
- DIFF_XDL_CLR(opts, NEED_MINIMAL);
opts->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
opts->xdl_opts |= value;
@@ -3544,7 +3820,8 @@ static void builtin_diff(const char *name_a,
const char *xfrm_msg,
int must_show_header,
struct diff_options *o,
- int complete_rewrite)
+ int complete_rewrite,
+ const struct range_set *line_ranges)
{
mmfile_t mf1, mf2;
const char *lbl[2];
@@ -3714,6 +3991,7 @@ static void builtin_diff(const char *name_a,
xpparam_t xpp;
xdemitconf_t xecfg;
struct emit_callback ecbdata;
+ unsigned ws_rule;
const struct userdiff_funcname *pe;
if (must_show_header) {
@@ -3725,6 +4003,12 @@ static void builtin_diff(const char *name_a,
mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr);
mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr);
+ ws_rule = whitespace_rule(o->repo->index, name_b);
+
+ /* symlink being an incomplete line is not a news */
+ if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+ ws_rule &= ~WS_INCOMPLETE_LINE;
+
pe = diff_funcname_pattern(o, one);
if (!pe)
pe = diff_funcname_pattern(o, two);
@@ -3736,7 +4020,7 @@ static void builtin_diff(const char *name_a,
lbl[0] = NULL;
ecbdata.label_path = lbl;
ecbdata.color_diff = o->use_color;
- ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b);
+ ecbdata.ws_rule = ws_rule;
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
check_blank_at_eof(&mf1, &mf2, &ecbdata);
ecbdata.opt = o;
@@ -3778,6 +4062,52 @@ static void builtin_diff(const char *name_a,
*/
xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
&ecbdata, &xpp, &xecfg);
+ } else if (line_ranges) {
+ struct line_range_callback lr_state;
+ unsigned int i;
+ long max_span = 0;
+
+ memset(&lr_state, 0, sizeof(lr_state));
+ lr_state.orig_line_fn = fn_out_consume;
+ lr_state.orig_cb_data = &ecbdata;
+ lr_state.ranges = line_ranges;
+ strbuf_init(&lr_state.rhunk, 0);
+ strbuf_init(&lr_state.pending_rm, 0);
+
+ /*
+ * Inflate ctxlen so that all changes within
+ * any single range are merged into one xdiff
+ * hunk and the inter-change context is emitted.
+ * The callback clips back to range boundaries.
+ *
+ * The optimal ctxlen depends on where changes
+ * fall within the range, which is only known
+ * after xdiff runs; the max range span is the
+ * upper bound that guarantees correctness in a
+ * single pass.
+ */
+ for (i = 0; i < line_ranges->nr; i++) {
+ long span = line_ranges->ranges[i].end -
+ line_ranges->ranges[i].start;
+ if (span > max_span)
+ max_span = span;
+ }
+ if (max_span > xecfg.ctxlen)
+ xecfg.ctxlen = max_span;
+
+ if (xdi_diff_outf(&mf1, &mf2,
+ line_range_hunk_fn,
+ line_range_line_fn,
+ &lr_state, &xpp, &xecfg))
+ die("unable to generate diff for %s",
+ one->path);
+
+ flush_rhunk(&lr_state);
+ if (lr_state.ret)
+ die("unable to generate diff for %s",
+ one->path);
+ strbuf_release(&lr_state.rhunk);
+ strbuf_release(&lr_state.pending_rm);
} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
&ecbdata, &xpp, &xecfg))
die("unable to generate diff for %s", one->path);
@@ -3943,6 +4273,10 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
data.ws_rule = whitespace_rule(o->repo->index, attr_path);
data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path);
+ /* symlink being an incomplete line is not a news */
+ if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode))
+ data.ws_rule &= ~WS_INCOMPLETE_LINE;
+
if (fill_mmfile(o->repo, &mf1, one) < 0 ||
fill_mmfile(o->repo, &mf2, two) < 0)
die("unable to read files to diff");
@@ -4615,7 +4949,7 @@ static void run_diff_cmd(const struct external_diff *pgm,
builtin_diff(name, other ? other : name,
one, two, xfrm_msg, must_show_header,
- o, complete_rewrite);
+ o, complete_rewrite, p->line_ranges);
if (p->status == DIFF_STATUS_COPIED ||
p->status == DIFF_STATUS_RENAMED)
o->found_changes = 1;
@@ -4987,6 +5321,8 @@ void diff_setup_done(struct diff_options *options)
if (options->flags.quick) {
options->output_format = DIFF_FORMAT_NO_OUTPUT;
options->flags.exit_with_status = 1;
+ options->detect_rename = 0;
+ options->flags.find_copies_harder = 0;
}
/*
@@ -5163,6 +5499,8 @@ static int diff_opt_find_object(const struct option *option,
struct object_id oid;
BUG_ON_OPT_NEG(unset);
+ if (!startup_info->have_repository)
+ return error(_("--find-object requires a git repository"));
if (repo_get_oid(the_repository, arg, &oid))
return error(_("unable to resolve '%s'"), arg);
@@ -7046,6 +7384,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
if (!diffopt->flags.no_index)
diffopt->skip_stat_unmatch++;
diff_free_filepair(p);
+ q->queue[i] = NULL;
}
}
free(q->queue);
@@ -7089,6 +7428,10 @@ void diff_queued_diff_prefetch(void *repository)
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
+
+ if (!p)
+ continue;
+
diff_add_if_missing(repo, &to_fetch, p->one);
diff_add_if_missing(repo, &to_fetch, p->two);
}
@@ -7124,7 +7467,7 @@ void diffcore_std(struct diff_options *options)
* If no prefetching occurs, diffcore_rename() will prefetch if it
* decides that it needs inexact rename detection.
*/
- if (options->repo == the_repository && repo_has_promisor_remote(the_repository) &&
+ if (repo_has_promisor_remote(options->repo) &&
(options->output_format & output_formats_to_prefetch ||
options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
diff_queued_diff_prefetch(options->repo);
@@ -7347,6 +7690,26 @@ void diff_change(struct diff_options *options,
concatpath, old_dirty_submodule, new_dirty_submodule);
}
+void diff_same(struct diff_options *options,
+ unsigned mode,
+ const struct object_id *oid,
+ const char *concatpath)
+{
+ struct diff_filespec *one;
+
+ if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
+ return;
+
+ if (options->prefix &&
+ strncmp(concatpath, options->prefix, options->prefix_length))
+ return;
+
+ one = alloc_filespec(concatpath);
+ fill_filespec(one, oid, 1, mode);
+ one->count++;
+ diff_queue(&diff_queued_diff, one, one);
+}
+
struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
{
struct diff_filepair *pair;