diff options
Diffstat (limited to 'diff.c')
-rw-r--r-- | diff.c | 366 |
1 files changed, 288 insertions, 78 deletions
@@ -13,6 +13,7 @@ #include "attr.h" #include "run-command.h" #include "utf8.h" +#include "object-store.h" #include "userdiff.h" #include "submodule-config.h" #include "submodule.h" @@ -22,6 +23,7 @@ #include "argv-array.h" #include "graph.h" #include "packfile.h" +#include "help.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -35,6 +37,7 @@ static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; static int diff_color_moved_default; +static int diff_color_moved_ws_default; static int diff_context_default = 3; static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; @@ -69,46 +72,37 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */ }; +static const char *color_diff_slots[] = { + [DIFF_CONTEXT] = "context", + [DIFF_METAINFO] = "meta", + [DIFF_FRAGINFO] = "frag", + [DIFF_FILE_OLD] = "old", + [DIFF_FILE_NEW] = "new", + [DIFF_COMMIT] = "commit", + [DIFF_WHITESPACE] = "whitespace", + [DIFF_FUNCINFO] = "func", + [DIFF_FILE_OLD_MOVED] = "oldMoved", + [DIFF_FILE_OLD_MOVED_ALT] = "oldMovedAlternative", + [DIFF_FILE_OLD_MOVED_DIM] = "oldMovedDimmed", + [DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed", + [DIFF_FILE_NEW_MOVED] = "newMoved", + [DIFF_FILE_NEW_MOVED_ALT] = "newMovedAlternative", + [DIFF_FILE_NEW_MOVED_DIM] = "newMovedDimmed", + [DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed", +}; + static NORETURN void die_want_option(const char *option_name) { die(_("option '%s' requires a value"), option_name); } +define_list_config_array_extra(color_diff_slots, {"plain"}); + static int parse_diff_color_slot(const char *var) { - if (!strcasecmp(var, "context") || !strcasecmp(var, "plain")) + if (!strcasecmp(var, "plain")) return DIFF_CONTEXT; - if (!strcasecmp(var, "meta")) - return DIFF_METAINFO; - if (!strcasecmp(var, "frag")) - return DIFF_FRAGINFO; - if (!strcasecmp(var, "old")) - return DIFF_FILE_OLD; - if (!strcasecmp(var, "new")) - return DIFF_FILE_NEW; - if (!strcasecmp(var, "commit")) - return DIFF_COMMIT; - if (!strcasecmp(var, "whitespace")) - return DIFF_WHITESPACE; - if (!strcasecmp(var, "func")) - return DIFF_FUNCINFO; - if (!strcasecmp(var, "oldmoved")) - return DIFF_FILE_OLD_MOVED; - if (!strcasecmp(var, "oldmovedalternative")) - return DIFF_FILE_OLD_MOVED_ALT; - if (!strcasecmp(var, "oldmoveddimmed")) - return DIFF_FILE_OLD_MOVED_DIM; - if (!strcasecmp(var, "oldmovedalternativedimmed")) - return DIFF_FILE_OLD_MOVED_ALT_DIM; - if (!strcasecmp(var, "newmoved")) - return DIFF_FILE_NEW_MOVED; - if (!strcasecmp(var, "newmovedalternative")) - return DIFF_FILE_NEW_MOVED_ALT; - if (!strcasecmp(var, "newmoveddimmed")) - return DIFF_FILE_NEW_MOVED_DIM; - if (!strcasecmp(var, "newmovedalternativedimmed")) - return DIFF_FILE_NEW_MOVED_ALT_DIM; - return -1; + return LOOKUP_CONFIG(color_diff_slots, var); } static int parse_dirstat_params(struct diff_options *options, const char *params_string, @@ -177,7 +171,7 @@ static int parse_submodule_params(struct diff_options *options, const char *valu return 0; } -static int git_config_rename(const char *var, const char *value) +int git_config_rename(const char *var, const char *value) { if (!value) return DIFF_DETECT_RENAME; @@ -271,6 +265,8 @@ static int parse_color_moved(const char *arg) return COLOR_MOVED_NO; else if (!strcmp(arg, "plain")) return COLOR_MOVED_PLAIN; + else if (!strcmp(arg, "blocks")) + return COLOR_MOVED_BLOCKS; else if (!strcmp(arg, "zebra")) return COLOR_MOVED_ZEBRA; else if (!strcmp(arg, "default")) @@ -278,7 +274,43 @@ static int parse_color_moved(const char *arg) else if (!strcmp(arg, "dimmed_zebra")) return COLOR_MOVED_ZEBRA_DIM; else - return error(_("color moved setting must be one of 'no', 'default', 'zebra', 'dimmed_zebra', 'plain'")); + return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed_zebra', 'plain'")); +} + +static int parse_color_moved_ws(const char *arg) +{ + int ret = 0; + struct string_list l = STRING_LIST_INIT_DUP; + struct string_list_item *i; + + string_list_split(&l, arg, ',', -1); + + for_each_string_list_item(i, &l) { + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, i->string); + strbuf_trim(&sb); + + if (!strcmp(sb.buf, "ignore-space-change")) + ret |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(sb.buf, "ignore-space-at-eol")) + ret |= XDF_IGNORE_WHITESPACE_AT_EOL; + else if (!strcmp(sb.buf, "ignore-all-space")) + ret |= XDF_IGNORE_WHITESPACE; + else if (!strcmp(sb.buf, "allow-indentation-change")) + ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE; + else + error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf); + + strbuf_release(&sb); + } + + if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) && + (ret & XDF_WHITESPACE_FLAGS)) + die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes")); + + string_list_clear(&l, 0); + + return ret; } int git_diff_ui_config(const char *var, const char *value, void *cb) @@ -294,6 +326,13 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_color_moved_default = cm; return 0; } + if (!strcmp(var, "diff.colormovedws")) { + int cm = parse_color_moved_ws(value); + if (cm < 0) + return -1; + diff_color_moved_ws_default = cm; + return 0; + } if (!strcmp(var, "diff.context")) { diff_context_default = git_config_int(var, value); if (diff_context_default < 0) @@ -705,16 +744,116 @@ struct moved_entry { struct hashmap_entry ent; const struct emitted_diff_symbol *es; struct moved_entry *next_line; + struct ws_delta *wsd; +}; + +/** + * The struct ws_delta holds white space differences between moved lines, i.e. + * between '+' and '-' lines that have been detected to be a move. + * The string contains the difference in leading white spaces, before the + * rest of the line is compared using the white space config for move + * coloring. The current_longer indicates if the first string in the + * comparision is longer than the second. + */ +struct ws_delta { + char *string; + unsigned int current_longer : 1; }; +#define WS_DELTA_INIT { NULL, 0 } + +static int compute_ws_delta(const struct emitted_diff_symbol *a, + const struct emitted_diff_symbol *b, + struct ws_delta *out) +{ + const struct emitted_diff_symbol *longer = a->len > b->len ? a : b; + const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a; + int d = longer->len - shorter->len; + + out->string = xmemdupz(longer->line, d); + out->current_longer = (a == longer); + + return !strncmp(longer->line + d, shorter->line, shorter->len); +} + +static int cmp_in_block_with_wsd(const struct diff_options *o, + const struct moved_entry *cur, + const struct moved_entry *match, + struct moved_entry *pmb, + int n) +{ + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + int al = cur->es->len, cl = l->len; + const char *a = cur->es->line, + *b = match->es->line, + *c = l->line; -static int moved_entry_cmp(const struct diff_options *diffopt, - const struct moved_entry *a, - const struct moved_entry *b, + int wslen; + + /* + * 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. + */ + if (strcmp(a, b)) + return 1; + + if (!pmb->wsd) + /* + * No white space delta was carried forward? This can happen + * when we exit early in this function and do not carry + * forward ws. + */ + return 1; + + /* + * The indent changes of the block are known and carried forward in + * pmb->wsd; however we need to check if the indent changes of the + * current line are still the same as before. + * + * To do so we need to compare 'l' to 'cur', adjusting the + * one of them for the white spaces, depending which was longer. + */ + + wslen = strlen(pmb->wsd->string); + if (pmb->wsd->current_longer) { + c += wslen; + cl -= wslen; + } else { + a += wslen; + al -= wslen; + } + + if (strcmp(a, c)) + return 1; + + return 0; +} + +static int moved_entry_cmp(const void *hashmap_cmp_fn_data, + const void *entry, + const void *entry_or_key, const void *keydata) { + const struct diff_options *diffopt = hashmap_cmp_fn_data; + const struct moved_entry *a = entry; + const struct moved_entry *b = entry_or_key; + unsigned flags = diffopt->color_moved_ws_handling + & XDF_WHITESPACE_FLAGS; + + 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, - diffopt->xdl_opts); + flags); } static struct moved_entry *prepare_entry(struct diff_options *o, @@ -722,10 +861,12 @@ static struct moved_entry *prepare_entry(struct diff_options *o, { 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; - ret->ent.hash = xdiff_hash_string(l->line, l->len, o->xdl_opts); + ret->ent.hash = xdiff_hash_string(l->line, l->len, flags); ret->es = l; ret->next_line = NULL; + ret->wsd = NULL; return ret; } @@ -762,6 +903,56 @@ static void add_lines_to_move_detection(struct diff_options *o, } } +static void pmb_advance_or_null(struct diff_options *o, + struct moved_entry *match, + struct hashmap *hm, + struct moved_entry **pmb, + int pmb_nr) +{ + int i; + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *prev = pmb[i]; + struct moved_entry *cur = (prev && prev->next_line) ? + prev->next_line : NULL; + if (cur && !hm->cmpfn(o, cur, match, NULL)) { + pmb[i] = cur; + } else { + pmb[i] = NULL; + } + } +} + +static void pmb_advance_or_null_multi_match(struct diff_options *o, + struct moved_entry *match, + struct hashmap *hm, + struct moved_entry **pmb, + int pmb_nr, int n) +{ + int i; + char *got_match = xcalloc(1, pmb_nr); + + for (; match; match = hashmap_get_next(hm, match)) { + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *prev = pmb[i]; + 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; + } + } + + for (i = 0; i < pmb_nr; i++) { + if (got_match[i]) { + /* Carry the white space delta forward */ + pmb[i]->next_line->wsd = pmb[i]->wsd; + pmb[i] = pmb[i]->next_line; + } else + pmb[i] = NULL; + } +} + static int shrink_potential_moved_blocks(struct moved_entry **pmb, int pmb_nr) { @@ -779,6 +970,10 @@ static int shrink_potential_moved_blocks(struct moved_entry **pmb, if (lp < pmb_nr && rp > -1 && lp < rp) { pmb[lp] = pmb[rp]; + if (pmb[rp]->wsd) { + free(pmb[rp]->wsd->string); + FREE_AND_NULL(pmb[rp]->wsd); + } pmb[rp] = NULL; rp--; lp++; @@ -836,19 +1031,18 @@ static void mark_color_as_moved(struct diff_options *o, struct moved_entry *key; struct moved_entry *match = NULL; struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; - int i; switch (l->s) { case DIFF_SYMBOL_PLUS: hm = del_lines; key = prepare_entry(o, n); - match = hashmap_get(hm, key, o); + match = hashmap_get(hm, key, NULL); free(key); break; case DIFF_SYMBOL_MINUS: hm = add_lines; key = prepare_entry(o, n); - match = hashmap_get(hm, key, o); + match = hashmap_get(hm, key, NULL); free(key); break; default: @@ -867,17 +1061,11 @@ static void mark_color_as_moved(struct diff_options *o, if (o->color_moved == COLOR_MOVED_PLAIN) continue; - /* Check any potential block runs, advance each or nullify */ - for (i = 0; i < pmb_nr; i++) { - struct moved_entry *p = pmb[i]; - struct moved_entry *pnext = (p && p->next_line) ? - p->next_line : NULL; - if (pnext && !hm->cmpfn(o, pnext, match, NULL)) { - pmb[i] = p->next_line; - } else { - pmb[i] = NULL; - } - } + 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); @@ -888,7 +1076,17 @@ static void mark_color_as_moved(struct diff_options *o, */ for (; match; match = hashmap_get_next(hm, match)) { ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc); - pmb[pmb_nr++] = match; + if (o->color_moved_ws_handling & + COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) { + struct ws_delta *wsd = xmalloc(sizeof(*match->wsd)); + if (compute_ws_delta(l, match->es, wsd)) { + match->wsd = wsd; + pmb[pmb_nr++] = match; + } else + free(wsd); + } else { + pmb[pmb_nr++] = match; + } } flipped_block = (flipped_block + 1) % 2; @@ -899,7 +1097,7 @@ static void mark_color_as_moved(struct diff_options *o, block_length++; - if (flipped_block) + if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS) l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; } adjust_last_block(o, n, block_length); @@ -1184,7 +1382,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, fputs(o->stat_sep, o->file); break; default: - die("BUG: unknown diff symbol"); + BUG("unknown diff symbol"); } strbuf_release(&sb); } @@ -1343,7 +1541,7 @@ static struct diff_tempfile *claim_diff_tempfile(void) { for (i = 0; i < ARRAY_SIZE(diff_temp); i++) if (!diff_temp[i].name) return diff_temp + i; - die("BUG: diff is failing to clean up its tempfiles"); + BUG("diff is failing to clean up its tempfiles"); } static void remove_tempfile(void) @@ -3472,7 +3670,7 @@ static int reuse_worktree_file(const char *name, const struct object_id *oid, in * objects however would tend to be slower as they need * to be individually opened and inflated. */ - if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash)) + if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid)) return 0; /* @@ -3638,7 +3836,8 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) else { enum object_type type; if (size_only || (flags & CHECK_BINARY)) { - type = sha1_object_info(s->oid.hash, &s->size); + type = oid_object_info(the_repository, &s->oid, + &s->size); if (type < 0) die("unable to read %s", oid_to_hex(&s->oid)); @@ -3649,7 +3848,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) return 0; } } - s->data = read_sha1_file(s->oid.hash, &type, &s->size); + s->data = read_object_file(&s->oid, &type, &s->size); if (!s->data) die("unable to read %s", oid_to_hex(&s->oid)); s->should_free = 1; @@ -3834,13 +4033,13 @@ static int similarity_index(struct diff_filepair *p) static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev) { if (startup_info->have_repository) - return find_unique_abbrev(oid->hash, abbrev); + return find_unique_abbrev(oid, abbrev); else { char *hex = oid_to_hex(oid); if (abbrev < 0) abbrev = FALLBACK_DEFAULT_ABBREV; - if (abbrev > GIT_SHA1_HEXSZ) - die("BUG: oid abbreviation out of range: %d", abbrev); + if (abbrev > the_hash_algo->hexsz) + BUG("oid abbreviation out of range: %d", abbrev); if (abbrev) hex[abbrev] = '\0'; return hex; @@ -3897,13 +4096,14 @@ static void fill_metainfo(struct strbuf *msg, *must_show_header = 0; } if (one && two && oidcmp(&one->oid, &two->oid)) { - int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV; + const unsigned hexsz = the_hash_algo->hexsz; + int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV; if (o->flags.binary) { mmfile_t mf; if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) || (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) - abbrev = 40; + abbrev = hexsz; } strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set, diff_abbrev_oid(&one->oid, abbrev), @@ -4130,6 +4330,7 @@ void diff_setup(struct diff_options *options) } options->color_moved = diff_color_moved_default; + options->color_moved_ws_handling = diff_color_moved_ws_default; } void diff_setup_done(struct diff_options *options) @@ -4138,6 +4339,11 @@ void diff_setup_done(struct diff_options *options) DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF | DIFF_FORMAT_NO_OUTPUT; + /* + * This must be signed because we're comparing against a potentially + * negative value. + */ + const int hexsz = the_hash_algo->hexsz; if (options->set_default) options->set_default(options); @@ -4218,8 +4424,8 @@ void diff_setup_done(struct diff_options *options) */ read_cache(); } - if (40 < options->abbrev) - options->abbrev = 40; /* full */ + if (hexsz < options->abbrev) + options->abbrev = hexsz; /* full */ /* * It does not make sense to show the first hit we happened @@ -4334,7 +4540,7 @@ static int stat_opt(struct diff_options *options, const char **av) int argcount = 1; if (!skip_prefix(arg, "--stat", &arg)) - die("BUG: stat option does not begin with --stat: %s", arg); + BUG("stat option does not begin with --stat: %s", arg); end = (char *)arg; switch (*arg) { @@ -4704,6 +4910,8 @@ int diff_opt_parse(struct diff_options *options, if (cm < 0) die("bad --color-moved argument: %s", arg); options->color_moved = cm; + } else if (skip_prefix(arg, "--color-moved-ws=", &arg)) { + options->color_moved_ws_handling = parse_color_moved_ws(arg); } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; @@ -4797,8 +5005,8 @@ int diff_opt_parse(struct diff_options *options, options->abbrev = strtoul(arg, NULL, 10); if (options->abbrev < MINIMUM_ABBREV) options->abbrev = MINIMUM_ABBREV; - else if (40 < options->abbrev) - options->abbrev = 40; + else if (the_hash_algo->hexsz < options->abbrev) + options->abbrev = the_hash_algo->hexsz; } else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) { options->a_prefix = optarg; @@ -4948,7 +5156,7 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len) const char *abbrev; /* Do we want all 40 hex characters? */ - if (len == GIT_SHA1_HEXSZ) + if (len == the_hash_algo->hexsz) return oid_to_hex(oid); /* An abbreviated value is fine, possibly followed by an ellipsis. */ @@ -4978,7 +5186,7 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len) * the automatic sizing is supposed to give abblen that ensures * uniqueness across all objects (statistically speaking). */ - if (abblen < GIT_SHA1_HEXSZ - 3) { + if (abblen < the_hash_algo->hexsz - 3) { static char hex[GIT_MAX_HEXSZ + 1]; if (len < abblen && abblen <= len + 2) xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, ".."); @@ -5519,7 +5727,7 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o) struct diff_queue_struct *q = &diff_queued_diff; if (WSEH_NEW & WS_RULE_MASK) - die("BUG: WS rules bit mask overlaps with diff symbol flags"); + BUG("WS rules bit mask overlaps with diff symbol flags"); if (o->color_moved) o->emitted_symbols = &esm; @@ -5534,10 +5742,12 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o) if (o->color_moved) { struct hashmap add_lines, del_lines; - hashmap_init(&del_lines, - (hashmap_cmp_fn)moved_entry_cmp, o, 0); - hashmap_init(&add_lines, - (hashmap_cmp_fn)moved_entry_cmp, o, 0); + 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); add_lines_to_move_detection(o, &add_lines, &del_lines); mark_color_as_moved(o, &add_lines, &del_lines); @@ -6053,7 +6263,7 @@ size_t fill_textconv(struct userdiff_driver *driver, } if (!driver->textconv) - die("BUG: fill_textconv called with non-textconv driver"); + BUG("fill_textconv called with non-textconv driver"); if (driver->textconv_cache && df->oid_valid) { *outbuf = notes_cache_get(driver->textconv_cache, |