diff options
-rw-r--r-- | apply.c | 186 | ||||
-rw-r--r-- | apply.h | 48 | ||||
-rw-r--r-- | diff.c | 5 | ||||
-rw-r--r-- | diff.h | 1 | ||||
-rw-r--r-- | range-diff.c | 124 | ||||
-rwxr-xr-x | t/t3206-range-diff.sh | 124 | ||||
-rw-r--r-- | t/t3206/history.export | 84 |
7 files changed, 409 insertions, 163 deletions
@@ -22,6 +22,12 @@ #include "rerere.h" #include "apply.h" +struct gitdiff_data { + struct strbuf *root; + int linenr; + int p_value; +}; + static void git_apply_config(void) { git_config_get_string_const("apply.whitespace", &apply_default_whitespace); @@ -201,40 +207,6 @@ struct fragment { #define BINARY_DELTA_DEFLATED 1 #define BINARY_LITERAL_DEFLATED 2 -/* - * This represents a "patch" to a file, both metainfo changes - * such as creation/deletion, filemode and content changes represented - * as a series of fragments. - */ -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ - int rejected; - unsigned ws_rule; - int lines_added, lines_deleted; - int score; - int extension_linenr; /* first line specifying delete/new/rename/copy */ - unsigned int is_toplevel_relative:1; - unsigned int inaccurate_eof:1; - unsigned int is_binary:1; - unsigned int is_copy:1; - unsigned int is_rename:1; - unsigned int recount:1; - unsigned int conflicted_threeway:1; - unsigned int direct_to_threeway:1; - unsigned int crlf_in_old:1; - struct fragment *fragments; - char *result; - size_t resultsize; - char old_oid_prefix[GIT_MAX_HEXSZ + 1]; - char new_oid_prefix[GIT_MAX_HEXSZ + 1]; - struct patch *next; - - /* three-way fallback result */ - struct object_id threeway_stage[3]; -}; - static void free_fragment_list(struct fragment *list) { while (list) { @@ -469,7 +441,7 @@ static char *squash_slash(char *name) return name; } -static char *find_name_gnu(struct apply_state *state, +static char *find_name_gnu(struct strbuf *root, const char *line, int p_value) { @@ -478,7 +450,7 @@ static char *find_name_gnu(struct apply_state *state, /* * Proposed "new-style" GNU patch/diff format; see - * http://marc.info/?l=git&m=112927316408690&w=2 + * https://public-inbox.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/ */ if (unquote_c_style(&name, line, NULL)) { strbuf_release(&name); @@ -495,8 +467,8 @@ static char *find_name_gnu(struct apply_state *state, } strbuf_remove(&name, 0, cp - name.buf); - if (state->root.len) - strbuf_insert(&name, 0, state->root.buf, state->root.len); + if (root->len) + strbuf_insert(&name, 0, root->buf, root->len); return squash_slash(strbuf_detach(&name, NULL)); } @@ -659,7 +631,7 @@ static size_t diff_timestamp_len(const char *line, size_t len) return line + len - end; } -static char *find_name_common(struct apply_state *state, +static char *find_name_common(struct strbuf *root, const char *line, const char *def, int p_value, @@ -702,30 +674,30 @@ static char *find_name_common(struct apply_state *state, return squash_slash(xstrdup(def)); } - if (state->root.len) { - char *ret = xstrfmt("%s%.*s", state->root.buf, len, start); + if (root->len) { + char *ret = xstrfmt("%s%.*s", root->buf, len, start); return squash_slash(ret); } return squash_slash(xmemdupz(start, len)); } -static char *find_name(struct apply_state *state, +static char *find_name(struct strbuf *root, const char *line, char *def, int p_value, int terminate) { if (*line == '"') { - char *name = find_name_gnu(state, line, p_value); + char *name = find_name_gnu(root, line, p_value); if (name) return name; } - return find_name_common(state, line, def, p_value, NULL, terminate); + return find_name_common(root, line, def, p_value, NULL, terminate); } -static char *find_name_traditional(struct apply_state *state, +static char *find_name_traditional(struct strbuf *root, const char *line, char *def, int p_value) @@ -734,7 +706,7 @@ static char *find_name_traditional(struct apply_state *state, size_t date_len; if (*line == '"') { - char *name = find_name_gnu(state, line, p_value); + char *name = find_name_gnu(root, line, p_value); if (name) return name; } @@ -742,10 +714,10 @@ static char *find_name_traditional(struct apply_state *state, len = strchrnul(line, '\n') - line; date_len = diff_timestamp_len(line, len); if (!date_len) - return find_name_common(state, line, def, p_value, NULL, TERM_TAB); + return find_name_common(root, line, def, p_value, NULL, TERM_TAB); len -= date_len; - return find_name_common(state, line, def, p_value, line + len, 0); + return find_name_common(root, line, def, p_value, line + len, 0); } /* @@ -759,7 +731,7 @@ static int guess_p_value(struct apply_state *state, const char *nameline) if (is_dev_null(nameline)) return -1; - name = find_name_traditional(state, nameline, NULL, 0); + name = find_name_traditional(&state->root, nameline, NULL, 0); if (!name) return -1; cp = strchr(name, '/'); @@ -883,17 +855,17 @@ static int parse_traditional_patch(struct apply_state *state, if (is_dev_null(first)) { patch->is_new = 1; patch->is_delete = 0; - name = find_name_traditional(state, second, NULL, state->p_value); + name = find_name_traditional(&state->root, second, NULL, state->p_value); patch->new_name = name; } else if (is_dev_null(second)) { patch->is_new = 0; patch->is_delete = 1; - name = find_name_traditional(state, first, NULL, state->p_value); + name = find_name_traditional(&state->root, first, NULL, state->p_value); patch->old_name = name; } else { char *first_name; - first_name = find_name_traditional(state, first, NULL, state->p_value); - name = find_name_traditional(state, second, first_name, state->p_value); + first_name = find_name_traditional(&state->root, first, NULL, state->p_value); + name = find_name_traditional(&state->root, second, first_name, state->p_value); free(first_name); if (has_epoch_timestamp(first)) { patch->is_new = 1; @@ -914,7 +886,7 @@ static int parse_traditional_patch(struct apply_state *state, return 0; } -static int gitdiff_hdrend(struct apply_state *state, +static int gitdiff_hdrend(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -933,14 +905,14 @@ static int gitdiff_hdrend(struct apply_state *state, #define DIFF_OLD_NAME 0 #define DIFF_NEW_NAME 1 -static int gitdiff_verify_name(struct apply_state *state, +static int gitdiff_verify_name(struct gitdiff_data *state, const char *line, int isnull, char **name, int side) { if (!*name && !isnull) { - *name = find_name(state, line, NULL, state->p_value, TERM_TAB); + *name = find_name(state->root, line, NULL, state->p_value, TERM_TAB); return 0; } @@ -949,7 +921,7 @@ static int gitdiff_verify_name(struct apply_state *state, if (isnull) return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), *name, state->linenr); - another = find_name(state, line, NULL, state->p_value, TERM_TAB); + another = find_name(state->root, line, NULL, state->p_value, TERM_TAB); if (!another || strcmp(another, *name)) { free(another); return error((side == DIFF_NEW_NAME) ? @@ -965,7 +937,7 @@ static int gitdiff_verify_name(struct apply_state *state, return 0; } -static int gitdiff_oldname(struct apply_state *state, +static int gitdiff_oldname(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -974,7 +946,7 @@ static int gitdiff_oldname(struct apply_state *state, DIFF_OLD_NAME); } -static int gitdiff_newname(struct apply_state *state, +static int gitdiff_newname(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -992,21 +964,21 @@ static int parse_mode_line(const char *line, int linenr, unsigned int *mode) return 0; } -static int gitdiff_oldmode(struct apply_state *state, +static int gitdiff_oldmode(struct gitdiff_data *state, const char *line, struct patch *patch) { return parse_mode_line(line, state->linenr, &patch->old_mode); } -static int gitdiff_newmode(struct apply_state *state, +static int gitdiff_newmode(struct gitdiff_data *state, const char *line, struct patch *patch) { return parse_mode_line(line, state->linenr, &patch->new_mode); } -static int gitdiff_delete(struct apply_state *state, +static int gitdiff_delete(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1016,7 +988,7 @@ static int gitdiff_delete(struct apply_state *state, return gitdiff_oldmode(state, line, patch); } -static int gitdiff_newfile(struct apply_state *state, +static int gitdiff_newfile(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1026,47 +998,47 @@ static int gitdiff_newfile(struct apply_state *state, return gitdiff_newmode(state, line, patch); } -static int gitdiff_copysrc(struct apply_state *state, +static int gitdiff_copysrc(struct gitdiff_data *state, const char *line, struct patch *patch) { patch->is_copy = 1; free(patch->old_name); - patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); return 0; } -static int gitdiff_copydst(struct apply_state *state, +static int gitdiff_copydst(struct gitdiff_data *state, const char *line, struct patch *patch) { patch->is_copy = 1; free(patch->new_name); - patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); return 0; } -static int gitdiff_renamesrc(struct apply_state *state, +static int gitdiff_renamesrc(struct gitdiff_data *state, const char *line, struct patch *patch) { patch->is_rename = 1; free(patch->old_name); - patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); return 0; } -static int gitdiff_renamedst(struct apply_state *state, +static int gitdiff_renamedst(struct gitdiff_data *state, const char *line, struct patch *patch) { patch->is_rename = 1; free(patch->new_name); - patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); return 0; } -static int gitdiff_similarity(struct apply_state *state, +static int gitdiff_similarity(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1076,7 +1048,7 @@ static int gitdiff_similarity(struct apply_state *state, return 0; } -static int gitdiff_dissimilarity(struct apply_state *state, +static int gitdiff_dissimilarity(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1086,7 +1058,7 @@ static int gitdiff_dissimilarity(struct apply_state *state, return 0; } -static int gitdiff_index(struct apply_state *state, +static int gitdiff_index(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1126,7 +1098,7 @@ static int gitdiff_index(struct apply_state *state, * This is normal for a diff that doesn't change anything: we'll fall through * into the next diff. Tell the parser to break out. */ -static int gitdiff_unrecognized(struct apply_state *state, +static int gitdiff_unrecognized(struct gitdiff_data *state, const char *line, struct patch *patch) { @@ -1137,17 +1109,17 @@ static int gitdiff_unrecognized(struct apply_state *state, * Skip p_value leading components from "line"; as we do not accept * absolute paths, return NULL in that case. */ -static const char *skip_tree_prefix(struct apply_state *state, +static const char *skip_tree_prefix(int p_value, const char *line, int llen) { int nslash; int i; - if (!state->p_value) + if (!p_value) return (llen && line[0] == '/') ? NULL : line; - nslash = state->p_value; + nslash = p_value; for (i = 0; i < llen; i++) { int ch = line[i]; if (ch == '/' && --nslash <= 0) @@ -1164,7 +1136,7 @@ static const char *skip_tree_prefix(struct apply_state *state, * creation or deletion of an empty file. In any of these cases, * both sides are the same name under a/ and b/ respectively. */ -static char *git_header_name(struct apply_state *state, +static char *git_header_name(int p_value, const char *line, int llen) { @@ -1184,7 +1156,7 @@ static char *git_header_name(struct apply_state *state, goto free_and_fail1; /* strip the a/b prefix including trailing slash */ - cp = skip_tree_prefix(state, first.buf, first.len); + cp = skip_tree_prefix(p_value, first.buf, first.len); if (!cp) goto free_and_fail1; strbuf_remove(&first, 0, cp - first.buf); @@ -1201,7 +1173,7 @@ static char *git_header_name(struct apply_state *state, if (*second == '"') { if (unquote_c_style(&sp, second, NULL)) goto free_and_fail1; - cp = skip_tree_prefix(state, sp.buf, sp.len); + cp = skip_tree_prefix(p_value, sp.buf, sp.len); if (!cp) goto free_and_fail1; /* They must match, otherwise ignore */ @@ -1212,7 +1184,7 @@ static char *git_header_name(struct apply_state *state, } /* unquoted second */ - cp = skip_tree_prefix(state, second, line + llen - second); + cp = skip_tree_prefix(p_value, second, line + llen - second); if (!cp) goto free_and_fail1; if (line + llen - cp != first.len || @@ -1227,7 +1199,7 @@ static char *git_header_name(struct apply_state *state, } /* unquoted first name */ - name = skip_tree_prefix(state, line, llen); + name = skip_tree_prefix(p_value, line, llen); if (!name) return NULL; @@ -1243,7 +1215,7 @@ static char *git_header_name(struct apply_state *state, if (unquote_c_style(&sp, second, NULL)) goto free_and_fail2; - np = skip_tree_prefix(state, sp.buf, sp.len); + np = skip_tree_prefix(p_value, sp.buf, sp.len); if (!np) goto free_and_fail2; @@ -1287,7 +1259,7 @@ static char *git_header_name(struct apply_state *state, */ if (!name[len + 1]) return NULL; /* no postimage name */ - second = skip_tree_prefix(state, name + len + 1, + second = skip_tree_prefix(p_value, name + len + 1, line_len - (len + 1)); if (!second) return NULL; @@ -1302,26 +1274,28 @@ static char *git_header_name(struct apply_state *state, } } -static int check_header_line(struct apply_state *state, struct patch *patch) +static int check_header_line(int linenr, struct patch *patch) { int extensions = (patch->is_delete == 1) + (patch->is_new == 1) + (patch->is_rename == 1) + (patch->is_copy == 1); if (extensions > 1) return error(_("inconsistent header lines %d and %d"), - patch->extension_linenr, state->linenr); + patch->extension_linenr, linenr); if (extensions && !patch->extension_linenr) - patch->extension_linenr = state->linenr; + patch->extension_linenr = linenr; return 0; } -/* Verify that we recognize the lines following a git header */ -static int parse_git_header(struct apply_state *state, - const char *line, - int len, - unsigned int size, - struct patch *patch) +int parse_git_diff_header(struct strbuf *root, + int *linenr, + int p_value, + const char *line, + int len, + unsigned int size, + struct patch *patch) { unsigned long offset; + struct gitdiff_data parse_hdr_state; /* A git diff has explicit new/delete information, so we don't guess */ patch->is_new = 0; @@ -1333,20 +1307,24 @@ static int parse_git_header(struct apply_state *state, * or removing or adding empty files), so we get * the default name from the header. */ - patch->def_name = git_header_name(state, line, len); - if (patch->def_name && state->root.len) { - char *s = xstrfmt("%s%s", state->root.buf, patch->def_name); + patch->def_name = git_header_name(p_value, line, len); + if (patch->def_name && root->len) { + char *s = xstrfmt("%s%s", root->buf, patch->def_name); free(patch->def_name); patch->def_name = s; } line += len; size -= len; - state->linenr++; - for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) { + (*linenr)++; + parse_hdr_state.root = root; + parse_hdr_state.linenr = *linenr; + parse_hdr_state.p_value = p_value; + + for (offset = len ; size > 0 ; offset += len, size -= len, line += len, (*linenr)++) { static const struct opentry { const char *str; - int (*fn)(struct apply_state *, const char *, struct patch *); + int (*fn)(struct gitdiff_data *, const char *, struct patch *); } optable[] = { { "@@ -", gitdiff_hdrend }, { "--- ", gitdiff_oldname }, @@ -1377,10 +1355,10 @@ static int parse_git_header(struct apply_state *state, int res; if (len < oplen || memcmp(p->str, line, oplen)) continue; - res = p->fn(state, line + oplen, patch); + res = p->fn(&parse_hdr_state, line + oplen, patch); if (res < 0) return -1; - if (check_header_line(state, patch)) + if (check_header_line(*linenr, patch)) return -1; if (res > 0) return offset; @@ -1561,7 +1539,9 @@ static int find_header(struct apply_state *state, * or mode change, so we handle that specially */ if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_header(state, line, len, size, patch); + int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr, + state->p_value, line, len, + size, patch); if (git_hdr_len < 0) return -128; if (git_hdr_len <= len) @@ -117,6 +117,40 @@ struct apply_state { int applied_after_fixing_ws; }; +/* + * This represents a "patch" to a file, both metainfo changes + * such as creation/deletion, filemode and content changes represented + * as a series of fragments. + */ +struct patch { + char *new_name, *old_name, *def_name; + unsigned int old_mode, new_mode; + int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ + int rejected; + unsigned ws_rule; + int lines_added, lines_deleted; + int score; + int extension_linenr; /* first line specifying delete/new/rename/copy */ + unsigned int is_toplevel_relative:1; + unsigned int inaccurate_eof:1; + unsigned int is_binary:1; + unsigned int is_copy:1; + unsigned int is_rename:1; + unsigned int recount:1; + unsigned int conflicted_threeway:1; + unsigned int direct_to_threeway:1; + unsigned int crlf_in_old:1; + struct fragment *fragments; + char *result; + size_t resultsize; + char old_oid_prefix[GIT_MAX_HEXSZ + 1]; + char new_oid_prefix[GIT_MAX_HEXSZ + 1]; + struct patch *next; + + /* three-way fallback result */ + struct object_id threeway_stage[3]; +}; + int apply_parse_options(int argc, const char **argv, struct apply_state *state, int *force_apply, int *options, @@ -128,6 +162,20 @@ void clear_apply_state(struct apply_state *state); int check_apply_state(struct apply_state *state, int force_apply); /* + * Parse a git diff header, starting at line. Fills the relevant + * metadata information in 'struct patch'. + * + * Returns -1 on failure, the length of the parsed header otherwise. + */ +int parse_git_diff_header(struct strbuf *root, + int *linenr, + int p_value, + const char *line, + int len, + unsigned int size, + struct patch *patch); + +/* * Some aspects of the apply behavior are controlled by the following * bits in the "options" parameter passed to apply_all_patches(). */ @@ -1673,7 +1673,10 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (ecbdata->opt->flags.dual_color_diffed_diffs) strbuf_addstr(&msgbuf, reverse); strbuf_addstr(&msgbuf, frag); - strbuf_add(&msgbuf, line, ep - line); + if (ecbdata->opt->flags.suppress_hunk_header_line_count) + strbuf_add(&msgbuf, atat, sizeof(atat)); + else + strbuf_add(&msgbuf, line, ep - line); strbuf_addstr(&msgbuf, reset); /* @@ -98,6 +98,7 @@ struct diff_flags { unsigned stat_with_summary; unsigned suppress_diff_headers; unsigned dual_color_diffed_diffs; + unsigned suppress_hunk_header_line_count; }; static inline void diff_flags_or(struct diff_flags *a, diff --git a/range-diff.c b/range-diff.c index 48b0e1b4ce..ba1e9a4265 100644 --- a/range-diff.c +++ b/range-diff.c @@ -10,6 +10,7 @@ #include "commit.h" #include "pretty.h" #include "userdiff.h" +#include "apply.h" struct patch_util { /* For the search for an exact match */ @@ -24,6 +25,17 @@ struct patch_util { struct object_id oid; }; +static size_t find_end_of_line(char *buffer, unsigned long size) +{ + char *eol = memchr(buffer, '\n', size); + + if (!eol) + return size; + + *eol = '\0'; + return eol + 1 - buffer; +} + /* * Reads the patches into a string list, with the `util` field being populated * as struct object_id (will need to be free()d). @@ -31,10 +43,12 @@ struct patch_util { static int read_patches(const char *range, struct string_list *list) { struct child_process cp = CHILD_PROCESS_INIT; - FILE *in; - struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT; struct patch_util *util = NULL; int in_header = 1; + char *line, *current_filename = NULL; + int offset, len; + size_t size; argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges", "--reverse", "--date-order", "--decorate=no", @@ -54,17 +68,20 @@ static int read_patches(const char *range, struct string_list *list) if (start_command(&cp)) return error_errno(_("could not start `log`")); - in = fdopen(cp.out, "r"); - if (!in) { + if (strbuf_read(&contents, cp.out, 0) < 0) { error_errno(_("could not read `log` output")); finish_command(&cp); return -1; } - while (strbuf_getline(&line, in) != EOF) { + line = contents.buf; + size = contents.len; + for (offset = 0; size > 0; offset += len, size -= len, line += len) { const char *p; - if (skip_prefix(line.buf, "commit ", &p)) { + len = find_end_of_line(line, size); + line[len - 1] = '\0'; + if (skip_prefix(line, "commit ", &p)) { if (util) { string_list_append(list, buf.buf)->util = util; strbuf_reset(&buf); @@ -75,8 +92,7 @@ static int read_patches(const char *range, struct string_list *list) free(util); string_list_clear(list, 1); strbuf_release(&buf); - strbuf_release(&line); - fclose(in); + strbuf_release(&contents); finish_command(&cp); return -1; } @@ -85,61 +101,95 @@ static int read_patches(const char *range, struct string_list *list) continue; } - if (starts_with(line.buf, "diff --git")) { + if (starts_with(line, "diff --git")) { + struct patch patch = { 0 }; + struct strbuf root = STRBUF_INIT; + int linenr = 0; + in_header = 0; strbuf_addch(&buf, '\n'); if (!util->diff_offset) util->diff_offset = buf.len; - strbuf_addch(&buf, ' '); - strbuf_addbuf(&buf, &line); + line[len - 1] = '\n'; + len = parse_git_diff_header(&root, &linenr, 1, line, + len, size, &patch); + if (len < 0) + die(_("could not parse git header '%.*s'"), (int)len, line); + strbuf_addstr(&buf, " ## "); + if (patch.is_new > 0) + strbuf_addf(&buf, "%s (new)", patch.new_name); + else if (patch.is_delete > 0) + strbuf_addf(&buf, "%s (deleted)", patch.old_name); + else if (patch.is_rename) + strbuf_addf(&buf, "%s => %s", patch.old_name, patch.new_name); + else + strbuf_addstr(&buf, patch.new_name); + + free(current_filename); + if (patch.is_delete > 0) + current_filename = xstrdup(patch.old_name); + else + current_filename = xstrdup(patch.new_name); + + if (patch.new_mode && patch.old_mode && + patch.old_mode != patch.new_mode) + strbuf_addf(&buf, " (mode change %06o => %06o)", + patch.old_mode, patch.new_mode); + + strbuf_addstr(&buf, " ##"); } else if (in_header) { - if (starts_with(line.buf, "Author: ")) { - strbuf_addbuf(&buf, &line); + if (starts_with(line, "Author: ")) { + strbuf_addstr(&buf, " ## Metadata ##\n"); + strbuf_addstr(&buf, line); strbuf_addstr(&buf, "\n\n"); - } else if (starts_with(line.buf, " ")) { - strbuf_rtrim(&line); - strbuf_addbuf(&buf, &line); + strbuf_addstr(&buf, " ## Commit message ##\n"); + } else if (starts_with(line, " ")) { + p = line + len - 2; + while (isspace(*p) && p >= line) + p--; + strbuf_add(&buf, line, p - line + 1); strbuf_addch(&buf, '\n'); } continue; - } else if (starts_with(line.buf, "@@ ")) + } else if (skip_prefix(line, "@@ ", &p)) { + p = strstr(p, "@@"); strbuf_addstr(&buf, "@@"); - else if (!line.buf[0] || starts_with(line.buf, "index ")) + if (current_filename && p[2]) + strbuf_addf(&buf, " %s:", current_filename); + if (p) + strbuf_addstr(&buf, p + 2); + } else if (!line[0]) /* * A completely blank (not ' \n', which is context) * line is not valid in a diff. We skip it * silently, because this neatly handles the blank * separator line between commits in git-log * output. - * - * We also want to ignore the diff's `index` lines - * because they contain exact blob hashes in which - * we are not interested. */ continue; - else if (line.buf[0] == '>') { + else if (line[0] == '>') { strbuf_addch(&buf, '+'); - strbuf_add(&buf, line.buf + 1, line.len - 1); - } else if (line.buf[0] == '<') { + strbuf_addstr(&buf, line + 1); + } else if (line[0] == '<') { strbuf_addch(&buf, '-'); - strbuf_add(&buf, line.buf + 1, line.len - 1); - } else if (line.buf[0] == '#') { + strbuf_addstr(&buf, line + 1); + } else if (line[0] == '#') { strbuf_addch(&buf, ' '); - strbuf_add(&buf, line.buf + 1, line.len - 1); + strbuf_addstr(&buf, line + 1); } else { strbuf_addch(&buf, ' '); - strbuf_addbuf(&buf, &line); + strbuf_addstr(&buf, line); } strbuf_addch(&buf, '\n'); util->diffsize++; } - fclose(in); - strbuf_release(&line); + strbuf_release(&contents); if (util) string_list_append(list, buf.buf)->util = util; strbuf_release(&buf); + free(current_filename); if (finish_command(&cp)) return -1; @@ -148,7 +198,7 @@ static int read_patches(const char *range, struct string_list *list) } static int patch_util_cmp(const void *dummy, const struct patch_util *a, - const struct patch_util *b, const char *keydata) + const struct patch_util *b, const char *keydata) { return strcmp(a->diff, keydata ? keydata : b->diff); } @@ -354,8 +404,9 @@ static void output_pair_header(struct diff_options *diffopt, fwrite(buf->buf, buf->len, 1, diffopt->file); } -static struct userdiff_driver no_func_name = { - .funcname = { "$^", 0 } +static struct userdiff_driver section_headers = { + .funcname = { "^ ## (.*) ##$\n" + "^.?@@ (.*)$", REG_EXTENDED } }; static struct diff_filespec *get_filespec(const char *name, const char *p) @@ -367,13 +418,13 @@ static struct diff_filespec *get_filespec(const char *name, const char *p) spec->size = strlen(p); spec->should_munmap = 0; spec->is_stdin = 1; - spec->driver = &no_func_name; + spec->driver = §ion_headers; return spec; } static void patch_diff(const char *a, const char *b, - struct diff_options *diffopt) + struct diff_options *diffopt) { diff_queue(&diff_queued_diff, get_filespec("a", a), get_filespec("b", b)); @@ -469,6 +520,7 @@ int show_range_diff(const char *range1, const char *range2, opts.output_format = DIFF_FORMAT_PATCH; opts.flags.suppress_diff_headers = 1; opts.flags.dual_color_diffed_diffs = dual_color; + opts.flags.suppress_hunk_header_line_count = 1; opts.output_prefix = output_prefix_cb; strbuf_addstr(&indent, " "); opts.output_prefix_data = &indent; diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index 048feaf6dd..ec548654ce 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -99,7 +99,7 @@ test_expect_success 'changed commit' ' 1: 4de457d = 1: a4b3333 s/5/A/ 2: fccce22 = 2: f51d370 s/4/A/ 3: 147e64e ! 3: 0559556 s/11/B/ - @@ -10,7 +10,7 @@ + @@ file: A 9 10 -11 @@ -109,8 +109,8 @@ test_expect_success 'changed commit' ' 13 14 4: a63e992 ! 4: d966c5c s/12/B/ - @@ -8,7 +8,7 @@ - @@ + @@ file + @@ file: A 9 10 - B @@ -158,7 +158,7 @@ test_expect_success 'changed commit with sm config' ' 1: 4de457d = 1: a4b3333 s/5/A/ 2: fccce22 = 2: f51d370 s/4/A/ 3: 147e64e ! 3: 0559556 s/11/B/ - @@ -10,7 +10,7 @@ + @@ file: A 9 10 -11 @@ -168,8 +168,8 @@ test_expect_success 'changed commit with sm config' ' 13 14 4: a63e992 ! 4: d966c5c s/12/B/ - @@ -8,7 +8,7 @@ - @@ + @@ file + @@ file: A 9 10 - B @@ -181,6 +181,92 @@ test_expect_success 'changed commit with sm config' ' test_cmp expected actual ' +test_expect_success 'renamed file' ' + git range-diff --no-color --submodule=log topic...renamed-file >actual && + sed s/Z/\ /g >expected <<-EOF && + 1: 4de457d = 1: f258d75 s/5/A/ + 2: fccce22 ! 2: 017b62d s/4/A/ + @@ Metadata + ZAuthor: Thomas Rast <trast@inf.ethz.ch> + Z + Z ## Commit message ## + - s/4/A/ + + s/4/A/ + rename file + Z + - ## file ## + + ## file => renamed-file ## + Z@@ + Z 1 + Z 2 + 3: 147e64e ! 3: 3ce7af6 s/11/B/ + @@ Metadata + Z ## Commit message ## + Z s/11/B/ + Z + - ## file ## + -@@ file: A + + ## renamed-file ## + +@@ renamed-file: A + Z 8 + Z 9 + Z 10 + 4: a63e992 ! 4: 1e6226b s/12/B/ + @@ Metadata + Z ## Commit message ## + Z s/12/B/ + Z + - ## file ## + -@@ file: A + + ## renamed-file ## + +@@ renamed-file: A + Z 9 + Z 10 + Z B + EOF + test_cmp expected actual +' + +test_expect_success 'file added and later removed' ' + git range-diff --no-color --submodule=log topic...added-removed >actual && + sed s/Z/\ /g >expected <<-EOF && + 1: 4de457d = 1: 096b1ba s/5/A/ + 2: fccce22 ! 2: d92e698 s/4/A/ + @@ Metadata + ZAuthor: Thomas Rast <trast@inf.ethz.ch> + Z + Z ## Commit message ## + - s/4/A/ + + s/4/A/ + new-file + Z + Z ## file ## + Z@@ + @@ file + Z A + Z 6 + Z 7 + + + + ## new-file (new) ## + 3: 147e64e ! 3: 9a1db4d s/11/B/ + @@ Metadata + ZAuthor: Thomas Rast <trast@inf.ethz.ch> + Z + Z ## Commit message ## + - s/11/B/ + + s/11/B/ + remove file + Z + Z ## file ## + Z@@ file: A + @@ file: A + Z 12 + Z 13 + Z 14 + + + + ## new-file (deleted) ## + 4: a63e992 = 4: fea3b5c s/12/B/ + EOF + test_cmp expected actual +' + test_expect_success 'no commits on one side' ' git commit --amend -m "new message" && git range-diff master HEAD@{1} HEAD @@ -191,15 +277,15 @@ test_expect_success 'changed message' ' sed s/Z/\ /g >expected <<-EOF && 1: 4de457d = 1: f686024 s/5/A/ 2: fccce22 ! 2: 4ab067d s/4/A/ - @@ -2,6 +2,8 @@ - Z + @@ Metadata + Z ## Commit message ## Z s/4/A/ Z + Also a silly comment here! + - Z diff --git a/file b/file - Z --- a/file - Z +++ b/file + Z ## file ## + Z@@ + Z 1 3: 147e64e = 3: b9cb956 s/11/B/ 4: a63e992 = 4: 8add5f1 s/12/B/ EOF @@ -210,17 +296,17 @@ test_expect_success 'dual-coloring' ' sed -e "s|^:||" >expect <<-\EOF && :<YELLOW>1: a4b3333 = 1: f686024 s/5/A/<RESET> :<RED>2: f51d370 <RESET><YELLOW>!<RESET><GREEN> 2: 4ab067d<RESET><YELLOW> s/4/A/<RESET> - : <REVERSE><CYAN>@@ -2,6 +2,8 @@<RESET> - : <RESET> + : <REVERSE><CYAN>@@<RESET> <RESET>Metadata<RESET> + : ## Commit message ##<RESET> : s/4/A/<RESET> : <RESET> : <REVERSE><GREEN>+<RESET><BOLD> Also a silly comment here!<RESET> : <REVERSE><GREEN>+<RESET> - : diff --git a/file b/file<RESET> - : --- a/file<RESET> - : +++ b/file<RESET> + : ## file ##<RESET> + : <CYAN> @@<RESET> + : 1<RESET> :<RED>3: 0559556 <RESET><YELLOW>!<RESET><GREEN> 3: b9cb956<RESET><YELLOW> s/11/B/<RESET> - : <REVERSE><CYAN>@@ -10,7 +10,7 @@<RESET> + : <REVERSE><CYAN>@@<RESET> <RESET>file: A<RESET> : 9<RESET> : 10<RESET> : <RED> -11<RESET> @@ -230,8 +316,8 @@ test_expect_success 'dual-coloring' ' : 13<RESET> : 14<RESET> :<RED>4: d966c5c <RESET><YELLOW>!<RESET><GREEN> 4: 8add5f1<RESET><YELLOW> s/12/B/<RESET> - : <REVERSE><CYAN>@@ -8,7 +8,7 @@<RESET> - : <CYAN> @@<RESET> + : <REVERSE><CYAN>@@<RESET> <RESET>file<RESET> + : <CYAN> @@ file: A<RESET> : 9<RESET> : 10<RESET> : <REVERSE><RED>-<RESET><FAINT> BB<RESET> diff --git a/t/t3206/history.export b/t/t3206/history.export index b8ffff0940..7bb3814962 100644 --- a/t/t3206/history.export +++ b/t/t3206/history.export @@ -22,8 +22,8 @@ data 51 19 20 -reset refs/heads/removed -commit refs/heads/removed +reset refs/heads/renamed-file +commit refs/heads/renamed-file mark :2 author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200 committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200 @@ -599,6 +599,82 @@ s/12/B/ from :46 M 100644 :28 file -reset refs/heads/removed -from :47 +commit refs/heads/added-removed +mark :48 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574151 +0100 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +blob +mark :49 +data 0 + +commit refs/heads/added-removed +mark :50 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574177 +0100 +data 18 +s/4/A/ + new-file +from :48 +M 100644 :5 file +M 100644 :49 new-file + +commit refs/heads/added-removed +mark :51 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574177 +0100 +data 22 +s/11/B/ + remove file +from :50 +M 100644 :7 file +D new-file + +commit refs/heads/added-removed +mark :52 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574177 +0100 +data 8 +s/12/B/ +from :51 +M 100644 :9 file + +commit refs/heads/renamed-file +mark :53 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574309 +0100 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/renamed-file +mark :54 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574312 +0100 +data 21 +s/4/A/ + rename file +from :53 +D file +M 100644 :5 renamed-file + +commit refs/heads/renamed-file +mark :55 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574319 +0100 +data 8 +s/11/B/ +from :54 +M 100644 :7 renamed-file + +commit refs/heads/renamed-file +mark :56 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Gummerer <t.gummerer@gmail.com> 1556574319 +0100 +data 8 +s/12/B/ +from :55 +M 100644 :9 renamed-file |