int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
static int diff_no_prefix;
+static int diff_stat_graph_width;
static int diff_dirstat_permille_default = 30;
static struct diff_options default_diff_options;
diff_no_prefix = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.statgraphwidth")) {
+ diff_stat_graph_width = git_config_int(var, value);
+ return 0;
+ }
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
if (!strcmp(var, "diff.wordregex"))
return 0;
}
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
+ if (userdiff_config(var, value) < 0)
+ return -1;
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
diff_words_show(ecbdata->diff_words);
}
+static void diff_filespec_load_driver(struct diff_filespec *one)
+{
+ /* Use already-loaded driver */
+ if (one->driver)
+ return;
+
+ if (S_ISREG(one->mode))
+ one->driver = userdiff_find_by_path(one->path);
+
+ /* Fallback to default settings */
+ if (!one->driver)
+ one->driver = userdiff_find_by_name("default");
+}
+
+static const char *userdiff_word_regex(struct diff_filespec *one)
+{
+ diff_filespec_load_driver(one);
+ return one->driver->word_regex;
+}
+
+static void init_diff_words_data(struct emit_callback *ecbdata,
+ struct diff_options *orig_opts,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ int i;
+ struct diff_options *o = xmalloc(sizeof(struct diff_options));
+ memcpy(o, orig_opts, sizeof(struct diff_options));
+
+ ecbdata->diff_words =
+ xcalloc(1, sizeof(struct diff_words_data));
+ ecbdata->diff_words->type = o->word_diff;
+ ecbdata->diff_words->opt = o;
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(one);
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(two);
+ if (!o->word_regex)
+ o->word_regex = diff_word_regex_cfg;
+ if (o->word_regex) {
+ ecbdata->diff_words->word_regex = (regex_t *)
+ xmalloc(sizeof(regex_t));
+ if (regcomp(ecbdata->diff_words->word_regex,
+ o->word_regex,
+ REG_EXTENDED | REG_NEWLINE))
+ die ("Invalid regular expression: %s",
+ o->word_regex);
+ }
+ for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
+ if (o->word_diff == diff_words_styles[i].type) {
+ ecbdata->diff_words->style =
+ &diff_words_styles[i];
+ break;
+ }
+ }
+ if (want_color(o->use_color)) {
+ struct diff_words_style *st = ecbdata->diff_words->style;
+ st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
+ st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
+ st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+ }
+}
+
static void free_diff_words_data(struct emit_callback *ecbdata)
{
if (ecbdata->diff_words) {
diff_words_flush(ecbdata);
+ free (ecbdata->diff_words->opt);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
diff_words_append(line, len,
&ecbdata->diff_words->plus);
return;
+ } else if (!prefixcmp(line, "\\ ")) {
+ /*
+ * Eat the "no newline at eof" marker as if we
+ * saw a "+" or "-" line with nothing on it,
+ * and return without diff_words_flush() to
+ * defer processing. If this is the end of
+ * preimage, more "+" lines may come after it.
+ */
+ return;
}
diff_words_flush(ecbdata);
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
static int scale_linear(int it, int width, int max_change)
{
+ if (!it)
+ return 0;
/*
- * make sure that at least one '-' is printed if there were deletions,
- * and likewise for '+'.
+ * make sure that at least one '-' or '+' is printed if
+ * there is any change to this path. The easiest way is to
+ * scale linearly as if the alloted width is one column shorter
+ * than it is, and then add 1 to the result.
*/
- if (max_change < 2)
- return it;
- return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+ return 1 + (it * (width - 1) / max_change);
}
static void show_name(FILE *file,
file->print_name = pname;
}
+int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!files) {
+ assert(insertions == 0 && deletions == 0);
+ return fputs(_(" 0 files changed\n"), fp);
+ }
+
+ strbuf_addf(&sb,
+ Q_(" %d file changed", " %d files changed", files),
+ files);
+
+ /*
+ * For binary diff, the caller may want to print "x files
+ * changed" with insertions == 0 && deletions == 0.
+ *
+ * Not omitting "0 insertions(+), 0 deletions(-)" in this case
+ * is probably less confusing (i.e skip over "2 files changed
+ * but nothing about added/removed lines? Is this a bug in Git?").
+ */
+ if (insertions || deletions == 0) {
+ /*
+ * TRANSLATORS: "+" in (+) is a line addition marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d insertion(+)", ", %d insertions(+)",
+ insertions),
+ insertions);
+ }
+
+ if (deletions || insertions == 0) {
+ /*
+ * TRANSLATORS: "-" in (-) is a line removal marker;
+ * do not translate it.
+ */
+ strbuf_addf(&sb,
+ Q_(", %d deletion(-)", ", %d deletions(-)",
+ deletions),
+ deletions);
+ }
+ strbuf_addch(&sb, '\n');
+ ret = fputs(sb.buf, fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
static void show_stats(struct diffstat_t *data, struct diff_options *options)
{
int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0;
int total_files = data->nr;
- int width, name_width, count;
+ int width, name_width, graph_width, number_width = 4, count;
const char *reset, *add_c, *del_c;
const char *line_prefix = "";
int extra_shown = 0;
line_prefix = msg->buf;
}
- width = options->stat_width ? options->stat_width : 80;
- name_width = options->stat_name_width ? options->stat_name_width : 50;
count = options->stat_count ? options->stat_count : data->nr;
- /* Sanity: give at least 5 columns to the graph,
- * but leave at least 10 columns for the name.
- */
- if (width < 25)
- width = 25;
- if (name_width < 10)
- name_width = 10;
- else if (width < name_width + 15)
- name_width = width - 15;
-
- /* Find the longest filename and max number of changes */
reset = diff_get_color_opt(options, DIFF_RESET);
add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
+ /*
+ * Find the longest filename and max number of changes
+ */
for (i = 0; (i < count) && (i < data->nr); i++) {
struct diffstat_file *file = data->files[i];
uintmax_t change = file->added + file->deleted;
}
count = i; /* min(count, data->nr) */
- /* Compute the width of the graph part;
- * 10 is for one blank at the beginning of the line plus
- * " | count " between the name and the graph.
+ /*
+ * We have width = stat_width or term_columns() columns total.
+ * 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)
+ * for " | NNNN " and one the empty column at the end, altogether
+ * 6 + decimal_width(max_change).
*
- * From here on, name_width is the width of the name area,
- * and width is the width of the graph area.
+ * If there's not enough space, we will use the smaller of
+ * stat_name_width (if set) and 5/8*width for the filename,
+ * and the rest for constant elements + graph part, but no more
+ * than stat_graph_width for the graph part.
+ * (5/8 gives 50 for filename and 30 for the constant parts + graph
+ * for the standard terminal size).
+ *
+ * In other words: stat_width limits the maximum width, and
+ * stat_name_width fixes the maximum width of the filename,
+ * and is also used to divide available columns if there
+ * aren't enough.
*/
- name_width = (name_width < max_len) ? name_width : max_len;
- if (width < (name_width + 10) + max_change)
- width = width - (name_width + 10);
+
+ if (options->stat_width == -1)
+ width = term_columns();
else
- width = max_change;
+ width = options->stat_width ? options->stat_width : 80;
+
+ if (options->stat_graph_width == -1)
+ options->stat_graph_width = diff_stat_graph_width;
+ /*
+ * Guarantee 3/8*16==6 for the graph part
+ * and 5/8*16==10 for the filename part
+ */
+ if (width < 16 + 6 + number_width)
+ width = 16 + 6 + number_width;
+
+ /*
+ * First assign sizes that are wanted, ignoring available width.
+ */
+ graph_width = (options->stat_graph_width &&
+ options->stat_graph_width < max_change) ?
+ options->stat_graph_width : max_change;
+ name_width = (options->stat_name_width > 0 &&
+ options->stat_name_width < max_len) ?
+ options->stat_name_width : max_len;
+
+ /*
+ * Adjust adjustable widths not to exceed maximum width
+ */
+ if (name_width + number_width + 6 + graph_width > width) {
+ if (graph_width > width * 3/8 - number_width - 6)
+ graph_width = width * 3/8 - number_width - 6;
+ if (options->stat_graph_width &&
+ graph_width > options->stat_graph_width)
+ graph_width = options->stat_graph_width;
+ if (name_width > width - number_width - 6 - graph_width)
+ name_width = width - number_width - 6 - graph_width;
+ else
+ graph_width = width - number_width - 6 - name_width;
+ }
+
+ /*
+ * From here name_width is the width of the name area,
+ * and graph_width is the width of the graph area.
+ * max_change is used to scale graph properly.
+ */
for (i = 0; i < count; i++) {
const char *prefix = "";
char *name = data->files[i]->print_name;
adds += add;
dels += del;
- if (width <= max_change) {
- add = scale_linear(add, width, max_change);
- del = scale_linear(del, width, max_change);
+ if (graph_width <= max_change) {
+ int total = add + del;
+
+ total = scale_linear(add + del, graph_width, max_change);
+ if (total < 2 && add && del)
+ /* width >= 2 due to the sanity check */
+ total = 2;
+ if (add < del) {
+ add = scale_linear(add, graph_width, max_change);
+ del = total - add;
+ } else {
+ del = scale_linear(del, graph_width, max_change);
+ add = total - del;
+ }
}
fprintf(options->file, "%s", line_prefix);
show_name(options->file, prefix, name, len);
extra_shown = 1;
}
fprintf(options->file, "%s", line_prefix);
- fprintf(options->file,
- " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
options->output_prefix_data);
fprintf(options->file, "%s", msg->buf);
}
- fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ print_stat_summary(options->file, total_files, adds, dels);
}
static void show_numstat(struct diffstat_t *data, struct diff_options *options)
emit_binary_diff_body(file, two, one, prefix);
}
-static void diff_filespec_load_driver(struct diff_filespec *one)
-{
- /* Use already-loaded driver */
- if (one->driver)
- return;
-
- if (S_ISREG(one->mode))
- one->driver = userdiff_find_by_path(one->path);
-
- /* Fallback to default settings */
- if (!one->driver)
- one->driver = userdiff_find_by_name("default");
-}
-
int diff_filespec_is_binary(struct diff_filespec *one)
{
if (one->is_binary == -1) {
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
-static const char *userdiff_word_regex(struct diff_filespec *one)
-{
- diff_filespec_load_driver(one);
- return one->driver->word_regex;
-}
-
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
if (!options->a_prefix)
struct emit_callback ecbdata;
const struct userdiff_funcname *pe;
- if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+ if (must_show_header) {
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
}
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
xecfg.flags = XDL_EMIT_FUNCNAMES;
+ if (DIFF_OPT_TST(o, FUNCCONTEXT))
+ xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
if (pe)
xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
if (!diffopts)
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
- if (o->word_diff) {
- int i;
-
- ecbdata.diff_words =
- xcalloc(1, sizeof(struct diff_words_data));
- ecbdata.diff_words->type = o->word_diff;
- ecbdata.diff_words->opt = o;
- if (!o->word_regex)
- o->word_regex = userdiff_word_regex(one);
- if (!o->word_regex)
- o->word_regex = userdiff_word_regex(two);
- if (!o->word_regex)
- o->word_regex = diff_word_regex_cfg;
- if (o->word_regex) {
- ecbdata.diff_words->word_regex = (regex_t *)
- xmalloc(sizeof(regex_t));
- if (regcomp(ecbdata.diff_words->word_regex,
- o->word_regex,
- REG_EXTENDED | REG_NEWLINE))
- die ("Invalid regular expression: %s",
- o->word_regex);
- }
- for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
- if (o->word_diff == diff_words_styles[i].type) {
- ecbdata.diff_words->style =
- &diff_words_styles[i];
- break;
- }
- }
- if (want_color(o->use_color)) {
- struct diff_words_style *st = ecbdata.diff_words->style;
- st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
- st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
- st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
- }
- }
+ if (o->word_diff)
+ init_diff_words_data(&ecbdata, o, one, two);
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg);
if (o->word_diff)
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
+ DIFF_OPT_SET(options, RENAME_EMPTY);
options->change = diff_change;
options->add_remove = diff_addremove;
char *end;
int width = options->stat_width;
int name_width = options->stat_name_width;
+ int graph_width = options->stat_graph_width;
int count = options->stat_count;
int argcount = 1;
name_width = strtoul(av[1], &end, 10);
argcount = 2;
}
+ } else if (!prefixcmp(arg, "-graph-width")) {
+ arg += strlen("-graph-width");
+ if (*arg == '=')
+ graph_width = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-graph-width' requires a value");
+ else if (!*arg) {
+ graph_width = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
} else if (!prefixcmp(arg, "-count")) {
arg += strlen("-count");
if (*arg == '=')
return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
options->stat_name_width = name_width;
+ options->stat_graph_width = graph_width;
options->stat_width = width;
options->stat_count = count;
return argcount;
}
else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0;
+ else if (!strcmp(arg, "--rename-empty"))
+ DIFF_OPT_SET(options, RENAME_EMPTY);
+ else if (!strcmp(arg, "--no-rename-empty"))
+ DIFF_OPT_CLR(options, RENAME_EMPTY);
else if (!strcmp(arg, "--relative"))
DIFF_OPT_SET(options, RELATIVE_NAME);
else if (!prefixcmp(arg, "--relative=")) {
else if (opt_arg(arg, '\0', "inter-hunk-context",
&options->interhunkcontext))
;
+ else if (!strcmp(arg, "-W"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--function-context"))
+ DIFF_OPT_SET(options, FUNCCONTEXT);
+ else if (!strcmp(arg, "--no-function-context"))
+ DIFF_OPT_CLR(options, FUNCCONTEXT);
else if ((argcount = parse_long_opt("output", av, &optarg))) {
options->file = fopen(optarg, "w");
if (!options->file)
if (output_format & DIFF_FORMAT_PATCH) {
if (separator) {
+ if (options->output_prefix) {
+ struct strbuf *msg = NULL;
+ msg = options->output_prefix(options,
+ options->output_prefix_data);
+ fwrite(msg->buf, msg->len, 1, stdout);
+ }
putc(options->line_termination, options->file);
if (options->stat_sep) {
/* attach patch instead of inline */
#include "refs.h"
#include "string-list.h"
#include "color.h"
+#include "gpg-interface.h"
struct decoration name_decoration = { "object names" };
type = DECORATION_REF_REMOTE;
else if (!prefixcmp(refname, "refs/tags/"))
type = DECORATION_REF_TAG;
- else if (!prefixcmp(refname, "refs/stash"))
+ else if (!strcmp(refname, "refs/stash"))
type = DECORATION_REF_STASH;
- else if (!prefixcmp(refname, "HEAD"))
+ else if (!strcmp(refname, "HEAD"))
type = DECORATION_REF_HEAD;
if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
}
}
+static void show_children(struct rev_info *opt, struct commit *commit, int abbrev)
+{
+ struct commit_list *p = lookup_decoration(&opt->children, &commit->object);
+ for ( ; p; p = p->next) {
+ printf(" %s", find_unique_abbrev(p->item->object.sha1, abbrev));
+ }
+}
+
void show_decorations(struct rev_info *opt, struct commit *commit)
{
const char *prefix;
*extra_headers_p = extra_headers;
}
+static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
+{
+ const char *color, *reset, *eol;
+
+ color = diff_get_color_opt(&opt->diffopt,
+ status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+ reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+ while (*bol) {
+ eol = strchrnul(bol, '\n');
+ printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+ *eol ? "\n" : "");
+ bol = (*eol) ? (eol + 1) : eol;
+ }
+}
+
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+ struct strbuf payload = STRBUF_INIT;
+ struct strbuf signature = STRBUF_INIT;
+ struct strbuf gpg_output = STRBUF_INIT;
+ int status;
+
+ if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+ goto out;
+
+ status = verify_signed_buffer(payload.buf, payload.len,
+ signature.buf, signature.len,
+ &gpg_output);
+ if (status && !gpg_output.len)
+ strbuf_addstr(&gpg_output, "No signature\n");
+
+ show_sig_lines(opt, status, gpg_output.buf);
+
+ out:
+ strbuf_release(&gpg_output);
+ strbuf_release(&payload);
+ strbuf_release(&signature);
+}
+
+static int which_parent(const unsigned char *sha1, const struct commit *commit)
+{
+ int nth;
+ const struct commit_list *parent;
+
+ for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
+ if (!hashcmp(parent->item->object.sha1, sha1))
+ return nth;
+ nth++;
+ }
+ return -1;
+}
+
+static int is_common_merge(const struct commit *commit)
+{
+ return (commit->parents
+ && commit->parents->next
+ && !commit->parents->next->next);
+}
+
+static void show_one_mergetag(struct rev_info *opt,
+ struct commit_extra_header *extra,
+ struct commit *commit)
+{
+ unsigned char sha1[20];
+ struct tag *tag;
+ struct strbuf verify_message;
+ int status, nth;
+ size_t payload_size, gpg_message_offset;
+
+ hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
+ tag = lookup_tag(sha1);
+ if (!tag)
+ return; /* error message already given */
+
+ strbuf_init(&verify_message, 256);
+ if (parse_tag_buffer(tag, extra->value, extra->len))
+ strbuf_addstr(&verify_message, "malformed mergetag\n");
+ else if (is_common_merge(commit) &&
+ !hashcmp(tag->tagged->sha1,
+ commit->parents->next->item->object.sha1))
+ strbuf_addf(&verify_message,
+ "merged tag '%s'\n", tag->tag);
+ else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
+ strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
+ tag->tag, tag->tagged->sha1);
+ else
+ strbuf_addf(&verify_message,
+ "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
+ gpg_message_offset = verify_message.len;
+
+ payload_size = parse_signature(extra->value, extra->len);
+ if ((extra->len <= payload_size) ||
+ (verify_signed_buffer(extra->value, payload_size,
+ extra->value + payload_size,
+ extra->len - payload_size,
+ &verify_message) &&
+ verify_message.len <= gpg_message_offset)) {
+ strbuf_addstr(&verify_message, "No signature\n");
+ status = -1;
+ }
+ else if (strstr(verify_message.buf + gpg_message_offset,
+ ": Good signature from "))
+ status = 0;
+ else
+ status = -1;
+
+ show_sig_lines(opt, status, verify_message.buf);
+ strbuf_release(&verify_message);
+}
+
+static void show_mergetag(struct rev_info *opt, struct commit *commit)
+{
+ struct commit_extra_header *extra, *to_free;
+
+ to_free = read_commit_extra_headers(commit, NULL);
+ for (extra = to_free; extra; extra = extra->next) {
+ if (strcmp(extra->key, "mergetag"))
+ continue; /* not a merge tag */
+ show_one_mergetag(opt, extra, commit);
+ }
+ free_commit_extra_headers(to_free);
+}
+
void show_log(struct rev_info *opt)
{
struct strbuf msgbuf = STRBUF_INIT;
fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
+ if (opt->children.name)
+ show_children(opt, commit, abbrev_commit);
show_decorations(opt, commit);
if (opt->graph && !graph_is_commit_finished(opt->graph)) {
putchar('\n');
stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
+ if (opt->children.name)
+ show_children(opt, commit, abbrev_commit);
if (parent)
printf(" (from %s)",
find_unique_abbrev(parent->object.sha1,
}
}
+ if (opt->show_signature) {
+ show_signature(opt, commit);
+ show_mergetag(opt, commit);
+ }
+
if (!commit->buffer)
return;
opt->verbose_header &&
opt->commit_format != CMIT_FMT_ONELINE) {
int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
- if ((pch & opt->diffopt.output_format) == pch)
- printf("---");
if (opt->diffopt.output_prefix) {
struct strbuf *msg = NULL;
msg = opt->diffopt.output_prefix(&opt->diffopt,
opt->diffopt.output_prefix_data);
fwrite(msg->buf, msg->len, 1, stdout);
}
+ if ((pch & opt->diffopt.output_format) == pch) {
+ printf("---");
+ }
putchar('\n');
}
}
static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
- unsigned const char *sha1 = commit->object.sha1;
-
- diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+ diff_tree_combined_merge(commit, opt->dense_combined_merges, opt);
return !opt->loginfo;
}
'
cat > expect <<\EOF
-* Merge commit 'reach'
+* Merge tag 'reach'
|\
| \
| \
-*-. \ Merge commit 'octopus-a'; commit 'octopus-b'
+*-. \ Merge tags 'octopus-a' and 'octopus-b'
|\ \ \
* | | | seventh
| | * | octopus-b
)
'
+ cat >expect <<\EOF
+ * commit COMMIT_OBJECT_NAME
+ |\ Merge: MERGE_PARENTS
+ | | Author: A U Thor <author@example.com>
+ | |
+ | | Merge HEADS DESCRIPTION
+ | |
+ | * commit COMMIT_OBJECT_NAME
+ | | Author: A U Thor <author@example.com>
+ | |
+ | | reach
+ | | ---
+ | | reach.t | 1 +
+ | | 1 file changed, 1 insertion(+)
+ | |
+ | | diff --git a/reach.t b/reach.t
+ | | new file mode 100644
+ | | index 0000000..10c9591
+ | | --- /dev/null
+ | | +++ b/reach.t
+ | | @@ -0,0 +1 @@
+ | | +reach
+ | |
+ | \
+ *-. \ commit COMMIT_OBJECT_NAME
+ |\ \ \ Merge: MERGE_PARENTS
+ | | | | Author: A U Thor <author@example.com>
+ | | | |
+ | | | | Merge HEADS DESCRIPTION
+ | | | |
+ | | * | commit COMMIT_OBJECT_NAME
+ | | |/ Author: A U Thor <author@example.com>
+ | | |
+ | | | octopus-b
+ | | | ---
+ | | | octopus-b.t | 1 +
+ | | | 1 file changed, 1 insertion(+)
+ | | |
+ | | | diff --git a/octopus-b.t b/octopus-b.t
+ | | | new file mode 100644
+ | | | index 0000000..d5fcad0
+ | | | --- /dev/null
+ | | | +++ b/octopus-b.t
+ | | | @@ -0,0 +1 @@
+ | | | +octopus-b
+ | | |
+ | * | commit COMMIT_OBJECT_NAME
+ | |/ Author: A U Thor <author@example.com>
+ | |
+ | | octopus-a
+ | | ---
+ | | octopus-a.t | 1 +
+ | | 1 file changed, 1 insertion(+)
+ | |
+ | | diff --git a/octopus-a.t b/octopus-a.t
+ | | new file mode 100644
+ | | index 0000000..11ee015
+ | | --- /dev/null
+ | | +++ b/octopus-a.t
+ | | @@ -0,0 +1 @@
+ | | +octopus-a
+ | |
+ * | commit COMMIT_OBJECT_NAME
+ |/ Author: A U Thor <author@example.com>
+ |
+ | seventh
+ | ---
+ | seventh.t | 1 +
+ | 1 file changed, 1 insertion(+)
+ |
+ | diff --git a/seventh.t b/seventh.t
+ | new file mode 100644
+ | index 0000000..9744ffc
+ | --- /dev/null
+ | +++ b/seventh.t
+ | @@ -0,0 +1 @@
+ | +seventh
+ |
+ * commit COMMIT_OBJECT_NAME
+ |\ Merge: MERGE_PARENTS
+ | | Author: A U Thor <author@example.com>
+ | |
+ | | Merge branch 'tangle'
+ | |
+ | * commit COMMIT_OBJECT_NAME
+ | |\ Merge: MERGE_PARENTS
+ | | | Author: A U Thor <author@example.com>
+ | | |
+ | | | Merge branch 'side' (early part) into tangle
+ | | |
+ | * | commit COMMIT_OBJECT_NAME
+ | |\ \ Merge: MERGE_PARENTS
+ | | | | Author: A U Thor <author@example.com>
+ | | | |
+ | | | | Merge branch 'master' (early part) into tangle
+ | | | |
+ | * | | commit COMMIT_OBJECT_NAME
+ | | | | Author: A U Thor <author@example.com>
+ | | | |
+ | | | | tangle-a
+ | | | | ---
+ | | | | tangle-a | 1 +
+ | | | | 1 file changed, 1 insertion(+)
+ | | | |
+ | | | | diff --git a/tangle-a b/tangle-a
+ | | | | new file mode 100644
+ | | | | index 0000000..7898192
+ | | | | --- /dev/null
+ | | | | +++ b/tangle-a
+ | | | | @@ -0,0 +1 @@
+ | | | | +a
+ | | | |
+ * | | | commit COMMIT_OBJECT_NAME
+ |\ \ \ \ Merge: MERGE_PARENTS
+ | | | | | Author: A U Thor <author@example.com>
+ | | | | |
+ | | | | | Merge branch 'side'
+ | | | | |
+ | * | | | commit COMMIT_OBJECT_NAME
+ | | |_|/ Author: A U Thor <author@example.com>
+ | |/| |
+ | | | | side-2
+ | | | | ---
+ | | | | 2 | 1 +
+ | | | | 1 file changed, 1 insertion(+)
+ | | | |
+ | | | | diff --git a/2 b/2
+ | | | | new file mode 100644
+ | | | | index 0000000..0cfbf08
+ | | | | --- /dev/null
+ | | | | +++ b/2
+ | | | | @@ -0,0 +1 @@
+ | | | | +2
+ | | | |
+ | * | | commit COMMIT_OBJECT_NAME
+ | | | | Author: A U Thor <author@example.com>
+ | | | |
+ | | | | side-1
+ | | | | ---
+ | | | | 1 | 1 +
+ | | | | 1 file changed, 1 insertion(+)
+ | | | |
+ | | | | diff --git a/1 b/1
+ | | | | new file mode 100644
+ | | | | index 0000000..d00491f
+ | | | | --- /dev/null
+ | | | | +++ b/1
+ | | | | @@ -0,0 +1 @@
+ | | | | +1
+ | | | |
+ * | | | commit COMMIT_OBJECT_NAME
+ | | | | Author: A U Thor <author@example.com>
+ | | | |
+ | | | | Second
+ | | | | ---
+ | | | | one | 1 +
+ | | | | 1 file changed, 1 insertion(+)
+ | | | |
+ | | | | diff --git a/one b/one
+ | | | | new file mode 100644
+ | | | | index 0000000..9a33383
+ | | | | --- /dev/null
+ | | | | +++ b/one
+ | | | | @@ -0,0 +1 @@
+ | | | | +case
+ | | | |
+ * | | | commit COMMIT_OBJECT_NAME
+ | |_|/ Author: A U Thor <author@example.com>
+ |/| |
+ | | | sixth
+ | | | ---
+ | | | a/two | 1 -
+ | | | 1 file changed, 1 deletion(-)
+ | | |
+ | | | diff --git a/a/two b/a/two
+ | | | deleted file mode 100644
+ | | | index 9245af5..0000000
+ | | | --- a/a/two
+ | | | +++ /dev/null
+ | | | @@ -1 +0,0 @@
+ | | | -ni
+ | | |
+ * | | commit COMMIT_OBJECT_NAME
+ | | | Author: A U Thor <author@example.com>
+ | | |
+ | | | fifth
+ | | | ---
+ | | | a/two | 1 +
+ | | | 1 file changed, 1 insertion(+)
+ | | |
+ | | | diff --git a/a/two b/a/two
+ | | | new file mode 100644
+ | | | index 0000000..9245af5
+ | | | --- /dev/null
+ | | | +++ b/a/two
+ | | | @@ -0,0 +1 @@
+ | | | +ni
+ | | |
+ * | | commit COMMIT_OBJECT_NAME
+ |/ / Author: A U Thor <author@example.com>
+ | |
+ | | fourth
+ | | ---
+ | | ein | 1 +
+ | | 1 file changed, 1 insertion(+)
+ | |
+ | | diff --git a/ein b/ein
+ | | new file mode 100644
+ | | index 0000000..9d7e69f
+ | | --- /dev/null
+ | | +++ b/ein
+ | | @@ -0,0 +1 @@
+ | | +ichi
+ | |
+ * | commit COMMIT_OBJECT_NAME
+ |/ Author: A U Thor <author@example.com>
+ |
+ | third
+ | ---
+ | ichi | 1 +
+ | one | 1 -
+ | 2 files changed, 1 insertion(+), 1 deletion(-)
+ |
+ | diff --git a/ichi b/ichi
+ | new file mode 100644
+ | index 0000000..9d7e69f
+ | --- /dev/null
+ | +++ b/ichi
+ | @@ -0,0 +1 @@
+ | +ichi
+ | diff --git a/one b/one
+ | deleted file mode 100644
+ | index 9d7e69f..0000000
+ | --- a/one
+ | +++ /dev/null
+ | @@ -1 +0,0 @@
+ | -ichi
+ |
+ * commit COMMIT_OBJECT_NAME
+ | Author: A U Thor <author@example.com>
+ |
+ | second
+ | ---
+ | one | 2 +-
+ | 1 file changed, 1 insertion(+), 1 deletion(-)
+ |
+ | diff --git a/one b/one
+ | index 5626abf..9d7e69f 100644
+ | --- a/one
+ | +++ b/one
+ | @@ -1 +1 @@
+ | -one
+ | +ichi
+ |
+ * commit COMMIT_OBJECT_NAME
+ Author: A U Thor <author@example.com>
+
+ initial
+ ---
+ one | 1 +
+ 1 file changed, 1 insertion(+)
+
+ diff --git a/one b/one
+ new file mode 100644
+ index 0000000..5626abf
+ --- /dev/null
+ +++ b/one
+ @@ -0,0 +1 @@
+ +one
+ EOF
+
+ sanitize_output () {
+ sed -e 's/ *$//' \
+ -e 's/commit [0-9a-f]*$/commit COMMIT_OBJECT_NAME/' \
+ -e 's/Merge: [ 0-9a-f]*$/Merge: MERGE_PARENTS/' \
+ -e 's/Merge tag.*/Merge HEADS DESCRIPTION/' \
+ -e 's/Merge commit.*/Merge HEADS DESCRIPTION/' \
+ -e 's/, 0 deletions(-)//' \
+ -e 's/, 0 insertions(+)//' \
+ -e 's/ 1 files changed, / 1 file changed, /' \
+ -e 's/, 1 deletions(-)/, 1 deletion(-)/' \
+ -e 's/, 1 insertions(+)/, 1 insertion(+)/'
+ }
+
+ test_expect_success 'log --graph with diff and stats' '
+ git log --graph --pretty=short --stat -p >actual &&
+ sanitize_output >actual.sanitized <actual &&
+ test_cmp expect actual.sanitized
+ '
+
test_done