#include "utf8.h"
#include "userdiff.h"
#include "sigchain.h"
+#include "submodule.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
GIT_COLOR_GREEN, /* NEW */
GIT_COLOR_YELLOW, /* COMMIT */
GIT_COLOR_BG_RED, /* WHITESPACE */
+ GIT_COLOR_NORMAL, /* FUNCINFO */
};
static void diff_filespec_load_driver(struct diff_filespec *one);
return DIFF_COMMIT;
if (!strcasecmp(var+ofs, "whitespace"))
return DIFF_WHITESPACE;
+ if (!strcasecmp(var+ofs, "func"))
+ return DIFF_FUNCINFO;
return -1;
}
struct diff_words_data *diff_words;
int *found_changesp;
FILE *file;
+ struct strbuf *header;
};
static int count_lines(const char *data, int size)
nofirst = 0;
}
- fputs(set, file);
-
- if (!nofirst)
- fputc(first, file);
- fwrite(line, len, 1, file);
- fputs(reset, file);
+ if (len || !nofirst) {
+ fputs(set, file);
+ if (!nofirst)
+ fputc(first, file);
+ fwrite(line, len, 1, file);
+ fputs(reset, file);
+ }
if (has_trailing_carriage_return)
fputc('\r', file);
if (has_trailing_newline)
}
}
+static void emit_hunk_header(struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
+ const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+ static const char atat[2] = { '@', '@' };
+ const char *cp, *ep;
+
+ /*
+ * As a hunk header must begin with "@@ -<old>, +<new> @@",
+ * it always is at least 10 bytes long.
+ */
+ if (len < 10 ||
+ memcmp(line, atat, 2) ||
+ !(ep = memmem(line + 2, len - 2, atat, 2))) {
+ emit_line(ecbdata->file, plain, reset, line, len);
+ return;
+ }
+ ep += 2; /* skip over @@ */
+
+ /* The hunk header in fraginfo color */
+ emit_line(ecbdata->file, frag, reset, line, ep - line);
+
+ /* blank before the func header */
+ for (cp = ep; ep - line < len; ep++)
+ if (*ep != ' ' && *ep != '\t')
+ break;
+ if (ep != cp)
+ emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+
+ if (ep < line + len)
+ emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+}
+
static struct diff_tempfile *claim_diff_tempfile(void) {
int i;
for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+ if (ecbdata->header) {
+ fprintf(ecbdata->file, "%s", ecbdata->header->buf);
+ strbuf_reset(ecbdata->header);
+ ecbdata->header = NULL;
+ }
*(ecbdata->found_changesp) = 1;
if (ecbdata->label_path[0]) {
diff_words_flush(ecbdata);
len = sane_truncate_line(ecbdata, line, len);
find_lno(line, ecbdata);
- emit_line(ecbdata->file,
- diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
- reset, line, len);
+ emit_hunk_header(ecbdata, line, len);
if (line[len-1] != '\n')
putc('\n', ecbdata->file);
return;
total_files, adds, dels);
}
-static void show_shortstats(struct diffstat_t* data, struct diff_options *options)
+static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
{
int i, adds = 0, dels = 0, total_files = data->nr;
const char *reset = diff_get_color_opt(o, DIFF_RESET);
const char *a_prefix, *b_prefix;
const char *textconv_one = NULL, *textconv_two = NULL;
+ struct strbuf header = STRBUF_INIT;
+
+ if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
+ (!one->mode || S_ISGITLINK(one->mode)) &&
+ (!two->mode || S_ISGITLINK(two->mode))) {
+ const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+ const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+ show_submodule_summary(o->file, one ? one->path : two->path,
+ one->sha1, two->sha1, two->dirty_submodule,
+ del, add, reset);
+ return;
+ }
if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
textconv_one = get_textconv(one);
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
- fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+ strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
if (lbl[0][0] == '/') {
/* /dev/null */
- fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
+ strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
if (xfrm_msg && xfrm_msg[0])
- fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+ strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
}
else if (lbl[1][0] == '/') {
- fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+ strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
if (xfrm_msg && xfrm_msg[0])
- fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+ strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
}
else {
if (one->mode != two->mode) {
- fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
- fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
+ strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+ strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
}
if (xfrm_msg && xfrm_msg[0])
- fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+ strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
+
/*
* we do not run diff between different kind
* of objects.
if (complete_rewrite &&
(textconv_one || !diff_filespec_is_binary(one)) &&
(textconv_two || !diff_filespec_is_binary(two))) {
+ fprintf(o->file, "%s", header.buf);
+ strbuf_reset(&header);
emit_rewrite_diff(name_a, name_b, one, two,
textconv_one, textconv_two, o);
o->found_changes = 1;
if (mf1.size == mf2.size &&
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
goto free_ab_and_return;
+ fprintf(o->file, "%s", header.buf);
+ strbuf_reset(&header);
if (DIFF_OPT_TST(o, BINARY))
emit_binary_diff(o->file, &mf1, &mf2);
else
struct emit_callback ecbdata;
const struct userdiff_funcname *pe;
+ if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
+ fprintf(o->file, "%s", header.buf);
+ strbuf_reset(&header);
+ }
+
if (textconv_one) {
size_t size;
mf1.ptr = run_textconv(textconv_one, one, &size);
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
check_blank_at_eof(&mf1, &mf2, &ecbdata);
ecbdata.file = o->file;
+ ecbdata.header = header.len ? &header : NULL;
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
}
free_ab_and_return:
+ strbuf_release(&header);
diff_free_filespec_data(one);
diff_free_filespec_data(two);
free(a_one);
* If ce is marked as "assume unchanged", there is no
* guarantee that work tree matches what we are looking for.
*/
- if (ce->ce_flags & CE_VALID)
+ if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
return 0;
/*
static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
{
int len;
- char *data = xmalloc(100);
+ char *data = xmalloc(100), *dirty = "";
+
+ /* Are we looking at the work tree? */
+ if (!s->sha1_valid && s->dirty_submodule)
+ dirty = "-dirty";
+
len = snprintf(data, 100,
- "Subproject commit %s\n", sha1_to_hex(s->sha1));
+ "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
s->data = data;
s->size = len;
s->should_free = 1;
}
*arg = NULL;
fflush(NULL);
- retval = run_command_v_opt(spawn_arg, 0);
+ retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
remove_tempfile();
if (retval) {
fprintf(stderr, "external diff died, stopping at %s.\n", name);
if (count > 1)
die("--name-only, --name-status, --check and -s are mutually exclusive");
+ /*
+ * Most of the time we can say "there are changes"
+ * only by checking if there are changed paths, but
+ * --ignore-whitespace* options force us to look
+ * inside contents.
+ */
+
+ if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+ DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+ DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+ DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+ else
+ DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
options->detect_rename = DIFF_DETECT_COPY;
* to have found. It does not make sense not to return with
* exit code in such a case either.
*/
- if (DIFF_OPT_TST(options, QUIET)) {
+ if (DIFF_OPT_TST(options, QUICK)) {
options->output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
}
DIFF_OPT_SET(options, FOLLOW_RENAMES);
else if (!strcmp(arg, "--color"))
DIFF_OPT_SET(options, COLOR_DIFF);
+ else if (!prefixcmp(arg, "--color=")) {
+ int value = git_config_colorbool(NULL, arg+8, -1);
+ if (value == 0)
+ DIFF_OPT_CLR(options, COLOR_DIFF);
+ else if (value > 0)
+ DIFF_OPT_SET(options, COLOR_DIFF);
+ else
+ return error("option `color' expects \"always\", \"auto\", or \"never\"");
+ }
else if (!strcmp(arg, "--no-color"))
DIFF_OPT_CLR(options, COLOR_DIFF);
else if (!strcmp(arg, "--color-words")) {
else if (!strcmp(arg, "--exit-code"))
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
else if (!strcmp(arg, "--quiet"))
- DIFF_OPT_SET(options, QUIET);
+ DIFF_OPT_SET(options, QUICK);
else if (!strcmp(arg, "--ext-diff"))
DIFF_OPT_SET(options, ALLOW_EXTERNAL);
else if (!strcmp(arg, "--no-ext-diff"))
DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
else if (!strcmp(arg, "--ignore-submodules"))
DIFF_OPT_SET(options, IGNORE_SUBMODULES);
+ else if (!strcmp(arg, "--submodule"))
+ DIFF_OPT_SET(options, SUBMODULE_LOG);
+ else if (!prefixcmp(arg, "--submodule=")) {
+ if (!strcmp(arg + 12, "log"))
+ DIFF_OPT_SET(options, SUBMODULE_LOG);
+ }
/* misc options */
else if (!strcmp(arg, "-z"))
separator++;
}
+ if (output_format & DIFF_FORMAT_NO_OUTPUT &&
+ DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
+ DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+ /*
+ * run diff_flush_patch for the exit status. setting
+ * options->file to /dev/null should be safe, becaue we
+ * aren't supposed to produce any output anyway.
+ */
+ if (options->close_file)
+ fclose(options->file);
+ options->file = fopen("/dev/null", "w");
+ if (!options->file)
+ die_errno("Could not open /dev/null");
+ options->close_file = 1;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (check_pair_status(p))
+ diff_flush_patch(p, options);
+ if (options->found_changes)
+ break;
+ }
+ }
+
if (output_format & DIFF_FORMAT_PATCH) {
if (separator) {
putc(options->line_termination, options->file);
q->nr = q->alloc = 0;
if (options->close_file)
fclose(options->file);
+
+ /*
+ * Report the content-level differences with HAS_CHANGES;
+ * diff_addremove/diff_change does not set the bit when
+ * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+ */
+ if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+ if (options->found_changes)
+ DIFF_OPT_SET(options, HAS_CHANGES);
+ else
+ DIFF_OPT_CLR(options, HAS_CHANGES);
+ }
}
static void diffcore_apply_filter(const char *filter)
struct diff_filepair *p = q->queue[i];
/*
- * 1. Entries that come from stat info dirtyness
+ * 1. Entries that come from stat info dirtiness
* always have both sides (iow, not create/delete),
* one side of the object name is unknown, with
* the same mode and size. Keep the ones that
*q = outq;
}
+static int diffnamecmp(const void *a_, const void *b_)
+{
+ const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+ const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+ const char *name_a, *name_b;
+
+ name_a = a->one ? a->one->path : a->two->path;
+ name_b = b->one ? b->one->path : b->two->path;
+ return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
void diffcore_std(struct diff_options *options)
{
if (options->skip_stat_unmatch)
diff_resolve_rename_copy();
diffcore_apply_filter(options->filter);
- if (diff_queued_diff.nr)
+ if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
DIFF_OPT_SET(options, HAS_CHANGES);
else
DIFF_OPT_CLR(options, HAS_CHANGES);
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *concatpath)
+ const char *concatpath, unsigned dirty_submodule)
{
struct diff_filespec *one, *two;
if (addremove != '+')
fill_filespec(one, sha1, mode);
- if (addremove != '-')
+ if (addremove != '-') {
fill_filespec(two, sha1, mode);
+ two->dirty_submodule = dirty_submodule;
+ }
diff_queue(&diff_queued_diff, one, two);
- DIFF_OPT_SET(options, HAS_CHANGES);
+ if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *concatpath)
+ const char *concatpath,
+ unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
struct diff_filespec *one, *two;
const unsigned char *tmp_c;
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+ tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
+ new_dirty_submodule = tmp;
}
if (options->prefix &&
two = alloc_filespec(concatpath);
fill_filespec(one, old_sha1, old_mode);
fill_filespec(two, new_sha1, new_mode);
+ one->dirty_submodule = old_dirty_submodule;
+ two->dirty_submodule = new_dirty_submodule;
diff_queue(&diff_queued_diff, one, two);
- DIFF_OPT_SET(options, HAS_CHANGES);
+ if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+ DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_unmerge(struct diff_options *options,
*arg = NULL;
memset(&child, 0, sizeof(child));
+ child.use_shell = 1;
child.argv = argv;
child.out = -1;
if (start_command(&child) != 0 ||