aboutsummaryrefslogtreecommitdiff
path: root/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'diff.c')
-rw-r--r--diff.c232
1 files changed, 160 insertions, 72 deletions
diff --git a/diff.c b/diff.c
index a1961526c0..e87847fa4b 100644
--- a/diff.c
+++ b/diff.c
@@ -601,6 +601,7 @@ 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;
@@ -796,21 +797,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 +1321,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 +1351,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 +1362,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 +1373,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 +1506,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 +1670,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 +1784,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 +1837,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 +1860,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 +1968,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 +2413,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 +2463,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;
}
@@ -2713,7 +2756,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 +2785,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 +2862,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 +3047,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 +3265,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 +3304,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 +3315,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 +3339,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 +3579,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;
@@ -3714,6 +3762,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 +3774,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 +3791,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;
@@ -3943,6 +3998,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");
@@ -4987,6 +5046,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 +5224,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 +7109,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 +7153,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 +7192,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 +7415,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;