/*
* Copyright (C) 2005 Junio C Hamano
*/
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
#include "cache.h"
#include "quote.h"
#include "diff.h"
#include "xdiff-interface.h"
#include "color.h"
+#ifdef NO_FAST_WORKING_DIRECTORY
+#define FAST_WORKING_DIRECTORY 0
+#else
+#define FAST_WORKING_DIRECTORY 1
+#endif
+
static int use_size_cache;
static int diff_detect_rename_default;
diff_rename_limit_default = git_config_int(var, value);
return 0;
}
- if (!strcmp(var, "diff.color")) {
+ if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
diff_use_color_default = git_config_colorbool(var, value);
return 0;
}
diff_detect_rename_default = DIFF_DETECT_RENAME;
return 0;
}
- if (!strncmp(var, "diff.color.", 11)) {
+ if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
color_parse(value, var, diff_colors[slot]);
return 0;
}
}
-static void copy_file(int prefix, const char *data, int size)
+static void copy_file(int prefix, const char *data, int size,
+ const char *set, const char *reset)
{
int ch, nl_just_seen = 1;
while (0 < size--) {
ch = *data++;
- if (nl_just_seen)
+ if (nl_just_seen) {
+ fputs(set, stdout);
putchar(prefix);
- putchar(ch);
- if (ch == '\n')
+ }
+ if (ch == '\n') {
nl_just_seen = 1;
- else
+ fputs(reset, stdout);
+ } else
nl_just_seen = 0;
+ putchar(ch);
}
if (!nl_just_seen)
- printf("\n\\ No newline at end of file\n");
+ printf("%s\n\\ No newline at end of file\n", reset);
}
static void emit_rewrite_diff(const char *name_a,
const char *name_b,
struct diff_filespec *one,
- struct diff_filespec *two)
+ struct diff_filespec *two,
+ int color_diff)
{
int lc_a, lc_b;
+ const char *name_a_tab, *name_b_tab;
+ const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
+ const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
+ const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
+ const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
+ const char *reset = diff_get_color(color_diff, DIFF_RESET);
+
+ name_a += (*name_a == '/');
+ name_b += (*name_b == '/');
+ name_a_tab = strchr(name_a, ' ') ? "\t" : "";
+ name_b_tab = strchr(name_b, ' ') ? "\t" : "";
+
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -",
+ metainfo, name_a, name_a_tab, reset,
+ metainfo, name_b, name_b_tab, reset, fraginfo);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
- printf(" @@\n");
+ printf(" @@%s\n", reset);
if (lc_a)
- copy_file('-', one->data, one->size);
+ copy_file('-', one->data, one->size, old, reset);
if (lc_b)
- copy_file('+', two->data, two->size);
+ copy_file('+', two->data, two->size, new, reset);
}
static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
int nparents, color_diff;
const char **label_path;
struct diff_words_data *diff_words;
+ int *found_changesp;
};
static void free_diff_words_data(struct emit_callback *ecbdata)
puts(reset);
}
-static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+static void emit_line_with_ws(int nparents,
+ const char *set, const char *reset, const char *ws,
+ const char *line, int len)
{
- int col0 = ecbdata->nparents;
+ int col0 = nparents;
int last_tab_in_indent = -1;
int last_space_in_indent = -1;
int i;
int tail = len;
int need_highlight_leading_space = 0;
- const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
- const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
-
- if (!*ws) {
- emit_line(set, reset, line, len);
- return;
- }
-
/* The line is a newly added line. Does it have funny leading
* whitespaces? In indent, SP should never precede a TAB.
*/
emit_line(set, reset, line + i, len - i);
}
+static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+{
+ const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+ const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+ if (!*ws)
+ emit_line(set, reset, line, len);
+ else
+ emit_line_with_ws(ecbdata->nparents, set, reset, ws,
+ line, len);
+}
+
static void fn_out_consume(void *priv, char *line, unsigned long len)
{
int i;
const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+ *(ecbdata->found_changesp) = 1;
+
if (ecbdata->label_path[0]) {
- printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
- printf("%s+++ %s%s\n", set, ecbdata->label_path[1], reset);
+ const char *name_a_tab, *name_b_tab;
+
+ name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
+ name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
+
+ printf("%s--- %s%s%s\n",
+ set, ecbdata->label_path[0], reset, name_a_tab);
+ printf("%s+++ %s%s%s\n",
+ set, ecbdata->label_path[1], reset, name_b_tab);
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
int pfx_length, sfx_length;
int len_a = strlen(a);
int len_b = strlen(b);
+ int qlen_a = quote_c_style(a, NULL, NULL, 0);
+ int qlen_b = quote_c_style(b, NULL, NULL, 0);
+
+ if (qlen_a || qlen_b) {
+ if (qlen_a) len_a = qlen_a;
+ if (qlen_b) len_b = qlen_b;
+ name = xmalloc( len_a + len_b + 5 );
+ if (qlen_a)
+ quote_c_style(a, name, NULL, 0);
+ else
+ memcpy(name, a, len_a);
+ memcpy(name + len_a, " => ", 4);
+ if (qlen_b)
+ quote_c_style(b, name + len_a + 4, NULL, 0);
+ else
+ memcpy(name + len_a + 4, b, len_b + 1);
+ return name;
+ }
/* Find common prefix */
pfx_length = 0;
x->deleted++;
}
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
const char mime_boundary_leader[] = "------------";
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+ /*
+ * make sure that at least one '-' is printed if there were deletions,
+ * and likewise for '+'.
+ */
+ if (max_change < 2)
+ return it;
+ return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+ const char *reset, const char *set)
+{
+ printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+ if (cnt <= 0)
+ return;
+ printf("%s", set);
+ while (cnt--)
+ putchar(ch);
+ printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
+ int max_change = 0, max_len = 0;
int total_files = data->nr;
+ int width, name_width;
+ const char *reset, *set, *add_c, *del_c;
if (data->nr == 0)
return;
+ width = options->stat_width ? options->stat_width : 80;
+ name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+ /* Sanity: give at least 5 columns to the graph,
+ * but leave at least 10 columns for the name.
+ */
+ if (width < name_width + 15) {
+ if (name_width <= 25)
+ width = name_width + 15;
+ else
+ name_width = width - 15;
+ }
+
+ /* Find the longest filename and max number of changes */
+ reset = diff_get_color(options->color_diff, DIFF_RESET);
+ set = diff_get_color(options->color_diff, DIFF_PLAIN);
+ add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+ del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
+ int change = file->added + file->deleted;
+
+ if (!file->is_renamed) { /* renames are already quoted by pprint_rename */
+ len = quote_c_style(file->name, NULL, NULL, 0);
+ if (len) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(file->name, qname, NULL, 0);
+ free(file->name);
+ file->name = qname;
+ }
+ }
len = strlen(file->name);
if (max_len < len)
if (file->is_binary || file->is_unmerged)
continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
+ if (max_change < change)
+ max_change = change;
}
+ /* 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.
+ *
+ * From here on, name_width is the width of the name area,
+ * and width is the width of the graph area.
+ */
+ name_width = (name_width < max_len) ? name_width : max_len;
+ if (width < (name_width + 10) + max_change)
+ width = width - (name_width + 10);
+ else
+ width = max_change;
+
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
char *name = data->files[i]->name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
+ int name_len;
/*
* "scale" the filename
*/
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
+ len = name_width;
+ name_len = strlen(name);
+ if (name_width < name_len) {
char *slash;
prefix = "...";
- max -= 3;
- name += len - max;
+ len -= 3;
+ name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;
if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Bin\n");
goto free_diffstat_file;
}
else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Unmerged\n");
goto free_diffstat_file;
}
else if (!data->files[i]->is_renamed &&
goto free_diffstat_file;
}
+ /*
+ * scale the add/delete
+ */
add = added;
del = deleted;
total = add + del;
adds += add;
dels += del;
- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
+ if (width <= max_change) {
+ add = scale_linear(add, width, max_change);
+ del = scale_linear(del, width, max_change);
+ total = add + del;
}
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
+ show_name(prefix, name, len, reset, set);
+ printf("%5d ", added + deleted);
+ show_graph('+', add, add_c, reset);
+ show_graph('-', del, del_c, reset);
+ putchar('\n');
free_diffstat_file:
free(data->files[i]->name);
free(data->files[i]);
}
free(data->files);
+ printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+ set, total_files, adds, dels, reset);
+}
+
+static void show_shortstats(struct diffstat_t* data)
+{
+ int i, adds = 0, dels = 0, total_files = data->nr;
+
+ if (data->nr == 0)
+ return;
+
+ for (i = 0; i < data->nr; i++) {
+ if (!data->files[i]->is_binary &&
+ !data->files[i]->is_unmerged) {
+ int added = data->files[i]->added;
+ int deleted= data->files[i]->deleted;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ } else {
+ adds += added;
+ dels += deleted;
+ }
+ }
+ free(data->files[i]->name);
+ free(data->files[i]);
+ }
+ free(data->files);
+
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ total_files, adds, dels);
+}
+
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+ int i;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ if (file->is_binary)
+ printf("-\t-\t");
+ else
+ printf("%d\t%d\t", file->added, file->deleted);
+ if (options->line_termination && !file->is_renamed &&
+ quote_c_style(file->name, NULL, NULL, 0))
+ quote_c_style(file->name, NULL, stdout, 0);
+ else
+ fputs(file->name, stdout);
+ putchar(options->line_termination);
+ }
}
struct checkdiff_t {
struct xdiff_emit_state xm;
const char *filename;
- int lineno;
+ int lineno, color_diff;
};
static void checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
+ const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
+ const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
+ const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
if (line[0] == '+') {
- int i, spaces = 0;
-
- data->lineno++;
+ int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0;
/* check space before tab */
for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
if (line[i] == ' ')
spaces++;
if (line[i - 1] == '\t' && spaces)
- printf("%s:%d: space before tab:%.*s\n",
- data->filename, data->lineno, (int)len, line);
+ space_before_tab = 1;
/* check white space at line end */
if (line[len - 1] == '\n')
len--;
if (isspace(line[len - 1]))
- printf("%s:%d: white space at end: %.*s\n",
- data->filename, data->lineno, (int)len, line);
+ white_space_at_end = 1;
+
+ if (space_before_tab || white_space_at_end) {
+ printf("%s:%d: %s", data->filename, data->lineno, ws);
+ if (space_before_tab) {
+ printf("space before tab");
+ if (white_space_at_end)
+ putchar(',');
+ }
+ if (white_space_at_end)
+ printf("white space at end");
+ printf(":%s ", reset);
+ emit_line_with_ws(1, set, reset, ws, line, len);
+ }
+
+ data->lineno++;
} else if (line[0] == ' ')
data->lineno++;
else if (line[0] == '@') {
const char *set = diff_get_color(o->color_diff, DIFF_METAINFO);
const char *reset = diff_get_color(o->color_diff, DIFF_RESET);
- a_one = quote_two("a/", name_a);
- b_two = quote_two("b/", name_b);
+ a_one = quote_two("a/", name_a + (*name_a == '/'));
+ b_two = quote_two("b/", name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
if ((one->mode ^ two->mode) & S_IFMT)
goto free_ab_and_return;
if (complete_rewrite) {
- emit_rewrite_diff(name_a, name_b, one, two);
+ emit_rewrite_diff(name_a, name_b, one, two,
+ o->color_diff);
+ o->found_changes = 1;
goto free_ab_and_return;
}
}
else
printf("Binary files %s and %s differ\n",
lbl[0], lbl[1]);
+ o->found_changes = 1;
}
else {
/* Crazy xdl interfaces.. */
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.label_path = lbl;
ecbdata.color_diff = o->color_diff;
+ ecbdata.found_changesp = &o->found_changes;
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.flags = XDL_EMIT_FUNCNAMES;
if (!diffopts)
;
- else if (!strncmp(diffopts, "--unified=", 10))
+ else if (!prefixcmp(diffopts, "--unified="))
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
- else if (!strncmp(diffopts, "-u", 2))
+ else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
ecb.outf = xdiff_outf;
ecb.priv = &ecbdata;
static void builtin_checkdiff(const char *name_a, const char *name_b,
struct diff_filespec *one,
- struct diff_filespec *two)
+ struct diff_filespec *two, struct diff_options *o)
{
mmfile_t mf1, mf2;
struct checkdiff_t data;
data.xm.consume = checkdiff_consume;
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
+ data.color_diff = o->color_diff;
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
* the work tree has that object contents, return true, so that
* prepare_temp_file() does not have to inflate and extract.
*/
-static int work_tree_matches(const char *name, const unsigned char *sha1)
+static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
{
struct cache_entry *ce;
struct stat st;
if (!active_cache)
return 0;
+ /* We want to avoid the working directory if our caller
+ * doesn't need the data in a normal file, this system
+ * is rather slow with its stat/open/mmap/close syscalls,
+ * and the object is contained in a pack file. The pack
+ * is probably already open and will be faster to obtain
+ * the data through than the working directory. Loose
+ * 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(sha1, NULL))
+ return 0;
+
len = strlen(name);
pos = cache_name_pos(name, len);
if (pos < 0)
return e;
}
+static int populate_from_stdin(struct diff_filespec *s)
+{
+#define INCREMENT 1024
+ char *buf;
+ unsigned long size;
+ int got;
+
+ size = 0;
+ buf = NULL;
+ while (1) {
+ buf = xrealloc(buf, size + INCREMENT);
+ got = xread(0, buf + size, INCREMENT);
+ if (!got)
+ break; /* EOF */
+ if (got < 0)
+ return error("error while reading from stdin %s",
+ strerror(errno));
+ size += got;
+ }
+ s->should_munmap = 0;
+ s->data = buf;
+ s->size = size;
+ s->should_free = 1;
+ return 0;
+}
+
/*
* While doing rename detection and pickaxe operation, we may need to
* grab the data for the blob (or file) for our own in-core comparison.
if (s->data)
return err;
if (!s->sha1_valid ||
- work_tree_matches(s->path, s->sha1)) {
+ reuse_worktree_file(s->path, s->sha1, 0)) {
struct stat st;
int fd;
+ char *buf;
+ unsigned long size;
+
+ if (!strcmp(s->path, "-"))
+ return populate_from_stdin(s);
+
if (lstat(s->path, &st) < 0) {
if (errno == ENOENT) {
err_empty:
return err;
}
}
- s->size = st.st_size;
+ s->size = xsize_t(st.st_size);
if (!s->size)
goto empty;
if (size_only)
fd = open(s->path, O_RDONLY);
if (fd < 0)
goto err_empty;
- s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+ s->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
- if (s->data == MAP_FAILED)
- goto err_empty;
s->should_munmap = 1;
+
+ /*
+ * Convert from working tree format to canonical git format
+ */
+ buf = s->data;
+ size = s->size;
+ if (convert_to_git(s->path, &buf, &size)) {
+ munmap(s->data, s->size);
+ s->should_munmap = 0;
+ s->data = buf;
+ s->size = size;
+ s->should_free = 1;
+ }
}
else {
- char type[20];
+ enum object_type type;
struct sha1_size_cache *e;
if (size_only) {
s->size = e->size;
return 0;
}
- if (!sha1_object_info(s->sha1, type, &s->size))
+ type = sha1_object_info(s->sha1, &s->size);
+ if (type < 0)
locate_size_cache(s->sha1, 0, s->size);
}
else {
- s->data = read_sha1_file(s->sha1, type, &s->size);
+ s->data = read_sha1_file(s->sha1, &type, &s->size);
s->should_free = 1;
}
}
fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
if (fd < 0)
die("unable to create temp-file");
- if (write(fd, blob, size) != size)
+ if (write_in_full(fd, blob, size) != size)
die("unable to write temp-file");
close(fd);
temp->name = temp->tmp_path;
}
if (!one->sha1_valid ||
- work_tree_matches(name, one->sha1)) {
+ reuse_worktree_file(name, one->sha1, 1)) {
struct stat st;
if (lstat(name, &st) < 0) {
if (errno == ENOENT)
if (S_ISLNK(st.st_mode)) {
int ret;
char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
+ size_t sz = xsize_t(st.st_size);
if (sizeof(buf) <= st.st_size)
die("symlink too long: %s", name);
- ret = readlink(name, buf, st.st_size);
+ ret = readlink(name, buf, sz);
if (ret < 0)
die("readlink(%s)", name);
- prep_temp_blob(temp, buf, st.st_size,
+ prep_temp_blob(temp, buf, sz,
(one->sha1_valid ?
one->sha1 : null_sha1),
(one->sha1_valid ?
if (DIFF_FILE_VALID(one)) {
if (!one->sha1_valid) {
struct stat st;
+ if (!strcmp(one->path, "-")) {
+ hashcpy(one->sha1, null_sha1);
+ return;
+ }
if (lstat(one->path, &st) < 0)
die("stat %s", one->path);
if (index_path(one->sha1, one->path, &st, 0))
diff_fill_sha1_info(p->one);
diff_fill_sha1_info(p->two);
- builtin_checkdiff(name, other, p->one, p->two);
+ builtin_checkdiff(name, other, p->one, p->two, o);
}
void diff_setup(struct diff_options *options)
DIFF_FORMAT_CHECKDIFF |
DIFF_FORMAT_NO_OUTPUT))
options->output_format &= ~(DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_SHORTSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_PATCH);
* recursive bits for other formats here.
*/
if (options->output_format & (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_SHORTSTAT |
+ DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_CHECKDIFF))
options->recursive = 1;
/*
if (options->abbrev <= 0 || 40 < options->abbrev)
options->abbrev = 40; /* full */
+ /*
+ * It does not make sense to show the first hit we happened
+ * to have found. It does not make sense not to return with
+ * exit code in such a case either.
+ */
+ if (options->quiet) {
+ options->output_format = DIFF_FORMAT_NO_OUTPUT;
+ options->exit_with_status = 1;
+ }
+
+ /*
+ * If we postprocess in diffcore, we cannot simply return
+ * upon the first hit. We need to run diff as usual.
+ */
+ if (options->pickaxe || options->filter)
+ options->quiet = 0;
+
return 0;
}
else if (!strcmp(arg, "--patch-with-raw")) {
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
}
- else if (!strcmp(arg, "--stat"))
+ else if (!strcmp(arg, "--numstat")) {
+ options->output_format |= DIFF_FORMAT_NUMSTAT;
+ }
+ else if (!strcmp(arg, "--shortstat")) {
+ options->output_format |= DIFF_FORMAT_SHORTSTAT;
+ }
+ else if (!prefixcmp(arg, "--stat")) {
+ char *end;
+ int width = options->stat_width;
+ int name_width = options->stat_name_width;
+ arg += 6;
+ end = (char *)arg;
+
+ switch (*arg) {
+ case '-':
+ if (!prefixcmp(arg, "-width="))
+ width = strtoul(arg + 7, &end, 10);
+ else if (!prefixcmp(arg, "-name-width="))
+ name_width = strtoul(arg + 12, &end, 10);
+ break;
+ case '=':
+ width = strtoul(arg+1, &end, 10);
+ if (*end == ',')
+ name_width = strtoul(end+1, &end, 10);
+ }
+
+ /* Important! This checks all the error cases! */
+ if (*end)
+ return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ options->stat_name_width = name_width;
+ options->stat_width = width;
+ }
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
}
else if (!strcmp(arg, "-z"))
options->line_termination = 0;
- else if (!strncmp(arg, "-l", 2))
+ else if (!prefixcmp(arg, "-l"))
options->rename_limit = strtoul(arg+2, NULL, 10);
else if (!strcmp(arg, "--full-index"))
options->full_index = 1;
options->output_format |= DIFF_FORMAT_NAME_STATUS;
else if (!strcmp(arg, "-R"))
options->reverse_diff = 1;
- else if (!strncmp(arg, "-S", 2))
+ else if (!prefixcmp(arg, "-S"))
options->pickaxe = arg + 2;
else if (!strcmp(arg, "-s")) {
options->output_format |= DIFF_FORMAT_NO_OUTPUT;
}
- else if (!strncmp(arg, "-O", 2))
+ else if (!prefixcmp(arg, "-O"))
options->orderfile = arg + 2;
- else if (!strncmp(arg, "--diff-filter=", 14))
+ else if (!prefixcmp(arg, "--diff-filter="))
options->filter = arg + 14;
else if (!strcmp(arg, "--pickaxe-all"))
options->pickaxe_opts = DIFF_PICKAXE_ALL;
else if (!strcmp(arg, "--pickaxe-regex"))
options->pickaxe_opts = DIFF_PICKAXE_REGEX;
- else if (!strncmp(arg, "-B", 2)) {
+ else if (!prefixcmp(arg, "-B")) {
if ((options->break_opt =
diff_scoreopt_parse(arg)) == -1)
return -1;
}
- else if (!strncmp(arg, "-M", 2)) {
+ else if (!prefixcmp(arg, "-M")) {
if ((options->rename_score =
diff_scoreopt_parse(arg)) == -1)
return -1;
options->detect_rename = DIFF_DETECT_RENAME;
}
- else if (!strncmp(arg, "-C", 2)) {
+ else if (!prefixcmp(arg, "-C")) {
if ((options->rename_score =
diff_scoreopt_parse(arg)) == -1)
return -1;
options->find_copies_harder = 1;
else if (!strcmp(arg, "--abbrev"))
options->abbrev = DEFAULT_ABBREV;
- else if (!strncmp(arg, "--abbrev=", 9)) {
+ else if (!prefixcmp(arg, "--abbrev=")) {
options->abbrev = strtoul(arg + 9, NULL, 10);
if (options->abbrev < MINIMUM_ABBREV)
options->abbrev = MINIMUM_ABBREV;
options->xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+ else if (!strcmp(arg, "--ignore-space-at-eol"))
+ options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
else if (!strcmp(arg, "--color-words"))
options->color_diff = options->color_diff_words = 1;
else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0;
+ else if (!strcmp(arg, "--exit-code"))
+ options->exit_with_status = 1;
+ else if (!strcmp(arg, "--quiet"))
+ options->quiet = 1;
else
return 0;
return 1;
/* user says num divided by scale and we say internally that
* is MAX_SCORE * num / scale.
*/
- return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
+ return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
}
int diff_scoreopt_parse(const char *opt)
free((void*)path_two);
}
-static void diff_flush_name(struct diff_filepair *p, int line_termination)
+static void diff_flush_name(struct diff_filepair *p, struct diff_options *opt)
{
char *path = p->two->path;
- if (line_termination)
+ if (opt->line_termination)
path = quote_one(p->two->path);
- printf("%s%c", path, line_termination);
+ printf("%s%c", path, opt->line_termination);
if (p->two->path != path)
free(path);
}
p->status = DIFF_STATUS_RENAMED;
}
else if (hashcmp(p->one->sha1, p->two->sha1) ||
- p->one->mode != p->two->mode)
+ p->one->mode != p->two->mode ||
+ is_null_sha1(p->one->sha1))
p->status = DIFF_STATUS_MODIFIED;
else {
/* This is a "no-change" entry and should not
else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
diff_flush_raw(p, opt);
else if (fmt & DIFF_FORMAT_NAME)
- diff_flush_name(p, opt->line_termination);
+ diff_flush_name(p, opt);
}
static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
{
+ char *name = quote_one(fs->path);
if (fs->mode)
- printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+ printf(" %s mode %06o %s\n", newdelete, fs->mode, name);
else
- printf(" %s %s\n", newdelete, fs->path);
+ printf(" %s %s\n", newdelete, name);
+ free(name);
}
static void show_mode_change(struct diff_filepair *p, int show_name)
{
if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
- if (show_name)
+ if (show_name) {
+ char *name = quote_one(p->two->path);
printf(" mode change %06o => %06o %s\n",
- p->one->mode, p->two->mode, p->two->path);
+ p->one->mode, p->two->mode, name);
+ free(name);
+ }
else
printf(" mode change %06o => %06o\n",
p->one->mode, p->two->mode);
static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
{
- const char *old, *new;
+ char *names = pprint_rename(p->one->path, p->two->path);
- /* Find common prefix */
- old = p->one->path;
- new = p->two->path;
- while (1) {
- const char *slash_old, *slash_new;
- slash_old = strchr(old, '/');
- slash_new = strchr(new, '/');
- if (!slash_old ||
- !slash_new ||
- slash_old - old != slash_new - new ||
- memcmp(old, new, slash_new - new))
- break;
- old = slash_old + 1;
- new = slash_new + 1;
- }
- /* p->one->path thru old is the common prefix, and old and new
- * through the end of names are renames
- */
- if (old != p->one->path)
- printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
- (int)(old - p->one->path), p->one->path,
- old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE));
- else
- printf(" %s %s => %s (%d%%)\n", renamecopy,
- p->one->path, p->two->path,
- (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ printf(" %s %s (%d%%)\n", renamecopy, names,
+ (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ free(names);
show_mode_change(p, 0);
}
break;
default:
if (p->score) {
- printf(" rewrite %s (%d%%)\n", p->two->path,
+ char *name = quote_one(p->two->path);
+ printf(" rewrite %s (%d%%)\n", name,
(int)(0.5 + p->score * 100.0/MAX_SCORE));
+ free(name);
show_mode_change(p, 0);
} else show_mode_change(p, 1);
break;
int new_len;
/* Ignore line numbers when computing the SHA1 of the patch */
- if (!strncmp(line, "@@ -", 4))
+ if (!prefixcmp(line, "@@ -"))
return;
new_len = remove_space(line, len);
separator++;
}
- if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ if (output_format & DIFF_FORMAT_NUMSTAT)
+ show_numstat(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIFFSTAT)
+ show_stats(&diffstat, options);
+ else if (output_format & DIFF_FORMAT_SHORTSTAT)
+ show_shortstats(&diffstat);
separator++;
}
void diffcore_std(struct diff_options *options)
{
+ if (options->quiet)
+ return;
if (options->break_opt != -1)
diffcore_break(options->break_opt);
if (options->detect_rename)
diffcore_order(options->orderfile);
diff_resolve_rename_copy();
diffcore_apply_filter(options->filter);
-}
-
-void diffcore_std_no_resolve(struct diff_options *options)
-{
- if (options->pickaxe)
- diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
- if (options->orderfile)
- diffcore_order(options->orderfile);
- diffcore_apply_filter(options->filter);
+ options->has_changes = !!diff_queued_diff.nr;
}
+
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
fill_filespec(two, sha1, mode);
diff_queue(&diff_queued_diff, one, two);
+ options->has_changes = 1;
}
void diff_change(struct diff_options *options,
fill_filespec(two, new_sha1, new_mode);
diff_queue(&diff_queued_diff, one, two);
+ options->has_changes = 1;
}
void diff_unmerge(struct diff_options *options,
- const char *path)
+ const char *path,
+ unsigned mode, const unsigned char *sha1)
{
struct diff_filespec *one, *two;
one = alloc_filespec(path);
two = alloc_filespec(path);
- diff_queue(&diff_queued_diff, one, two);
+ fill_filespec(one, sha1, mode);
+ diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
}