#include "quote.h"
#include "diff.h"
#include "diffcore.h"
-
-static const char *diff_opts = "-pu";
+#include "delta.h"
+#include "xdiff-interface.h"
static int use_size_cache;
-int diff_rename_limit_default = -1;
+static int diff_detect_rename_default = 0;
+static int diff_rename_limit_default = -1;
+static int diff_use_color_default = 0;
+
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+static char diff_colors[][24] = {
+ "\033[m", /* reset */
+ "", /* normal */
+ "\033[1m", /* bold */
+ "\033[36m", /* cyan */
+ "\033[31m", /* red */
+ "\033[32m", /* green */
+ "\033[33m" /* yellow */
+};
+
+static int parse_diff_color_slot(const char *var, int ofs)
+{
+ if (!strcasecmp(var+ofs, "plain"))
+ return DIFF_PLAIN;
+ if (!strcasecmp(var+ofs, "meta"))
+ return DIFF_METAINFO;
+ if (!strcasecmp(var+ofs, "frag"))
+ return DIFF_FRAGINFO;
+ if (!strcasecmp(var+ofs, "old"))
+ return DIFF_FILE_OLD;
+ if (!strcasecmp(var+ofs, "new"))
+ return DIFF_FILE_NEW;
+ if (!strcasecmp(var+ofs, "commit"))
+ return DIFF_COMMIT;
+ die("bad config variable '%s'", var);
+}
+
+static int parse_color(const char *name, int len)
+{
+ static const char * const color_names[] = {
+ "normal", "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white"
+ };
+ char *end;
+ int i;
+ for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+ const char *str = color_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return i - 1;
+ }
+ i = strtol(name, &end, 10);
+ if (*name && !*end && i >= -1 && i <= 255)
+ return i;
+ return -2;
+}
-int git_diff_config(const char *var, const char *value)
+static int parse_attr(const char *name, int len)
+{
+ static const int attr_values[] = { 1, 2, 4, 5, 7 };
+ static const char * const attr_names[] = {
+ "bold", "dim", "ul", "blink", "reverse"
+ };
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+ const char *str = attr_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return attr_values[i];
+ }
+ return -1;
+}
+
+static void parse_diff_color_value(const char *value, const char *var, char *dst)
+{
+ const char *ptr = value;
+ int attr = -1;
+ int fg = -2;
+ int bg = -2;
+
+ if (!strcasecmp(value, "reset")) {
+ strcpy(dst, "\033[m");
+ return;
+ }
+
+ /* [fg [bg]] [attr] */
+ while (*ptr) {
+ const char *word = ptr;
+ int val, len = 0;
+
+ while (word[len] && !isspace(word[len]))
+ len++;
+
+ ptr = word + len;
+ while (*ptr && isspace(*ptr))
+ ptr++;
+
+ val = parse_color(word, len);
+ if (val >= -1) {
+ if (fg == -2) {
+ fg = val;
+ continue;
+ }
+ if (bg == -2) {
+ bg = val;
+ continue;
+ }
+ goto bad;
+ }
+ val = parse_attr(word, len);
+ if (val < 0 || attr != -1)
+ goto bad;
+ attr = val;
+ }
+
+ if (attr >= 0 || fg >= 0 || bg >= 0) {
+ int sep = 0;
+
+ *dst++ = '\033';
+ *dst++ = '[';
+ if (attr >= 0) {
+ *dst++ = '0' + attr;
+ sep++;
+ }
+ if (fg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (fg < 8) {
+ *dst++ = '3';
+ *dst++ = '0' + fg;
+ } else {
+ dst += sprintf(dst, "38;5;%d", fg);
+ }
+ }
+ if (bg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (bg < 8) {
+ *dst++ = '4';
+ *dst++ = '0' + bg;
+ } else {
+ dst += sprintf(dst, "48;5;%d", bg);
+ }
+ }
+ *dst++ = 'm';
+ }
+ *dst = 0;
+ return;
+bad:
+ die("bad config value '%s' for variable '%s'", value, var);
+}
+
+/*
+ * These are to give UI layer defaults.
+ * The core-level commands such as git-diff-files should
+ * never be affected by the setting of diff.renames
+ * the user happens to have in the configuration file.
+ */
+int git_diff_ui_config(const char *var, const char *value)
{
if (!strcmp(var, "diff.renamelimit")) {
diff_rename_limit_default = git_config_int(var, value);
return 0;
}
-
+ if (!strcmp(var, "diff.color")) {
+ if (!value)
+ diff_use_color_default = 1; /* bool */
+ else if (!strcasecmp(value, "auto")) {
+ diff_use_color_default = 0;
+ if (isatty(1) || (pager_in_use && pager_use_color)) {
+ char *term = getenv("TERM");
+ if (term && strcmp(term, "dumb"))
+ diff_use_color_default = 1;
+ }
+ }
+ else if (!strcasecmp(value, "never"))
+ diff_use_color_default = 0;
+ else if (!strcasecmp(value, "always"))
+ diff_use_color_default = 1;
+ else
+ diff_use_color_default = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "diff.renames")) {
+ if (!value)
+ diff_detect_rename_default = DIFF_DETECT_RENAME;
+ else if (!strcasecmp(value, "copies") ||
+ !strcasecmp(value, "copy"))
+ diff_detect_rename_default = DIFF_DETECT_COPY;
+ else if (git_config_bool(var,value))
+ diff_detect_rename_default = DIFF_DETECT_RENAME;
+ return 0;
+ }
+ if (!strncmp(var, "diff.color.", 11)) {
+ int slot = parse_diff_color_slot(var, 11);
+ parse_diff_color_value(value, var, diff_colors[slot]);
+ return 0;
+ }
return git_default_config(var, value);
}
{
static const char *external_diff_cmd = NULL;
static int done_preparing = 0;
- const char *env_diff_opts;
if (done_preparing)
return external_diff_cmd;
-
- /*
- * Default values above are meant to match the
- * Linux kernel development style. Examples of
- * alternative styles you can specify via environment
- * variables are:
- *
- * GIT_DIFF_OPTS="-c";
- */
external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
-
- /* In case external diff fails... */
- env_diff_opts = getenv("GIT_DIFF_OPTS");
- if (env_diff_opts) diff_opts = env_diff_opts;
-
done_preparing = 1;
return external_diff_cmd;
}
char tmp_path[TEMPFILE_PATH_LEN];
} diff_temp[2];
-static int count_lines(const char *filename)
+static int count_lines(const char *data, int size)
{
- FILE *in;
int count, ch, completely_empty = 1, nl_just_seen = 0;
- in = fopen(filename, "r");
count = 0;
- while ((ch = fgetc(in)) != EOF)
+ while (0 < size--) {
+ ch = *data++;
if (ch == '\n') {
count++;
nl_just_seen = 1;
nl_just_seen = 0;
completely_empty = 0;
}
- fclose(in);
+ }
if (completely_empty)
return 0;
if (!nl_just_seen)
}
}
-static void copy_file(int prefix, const char *filename)
+static void copy_file(int prefix, const char *data, int size)
{
- FILE *in;
int ch, nl_just_seen = 1;
- in = fopen(filename, "r");
- while ((ch = fgetc(in)) != EOF) {
+ while (0 < size--) {
+ ch = *data++;
if (nl_just_seen)
putchar(prefix);
putchar(ch);
else
nl_just_seen = 0;
}
- fclose(in);
if (!nl_just_seen)
printf("\n\\ No newline at end of file\n");
}
static void emit_rewrite_diff(const char *name_a,
const char *name_b,
- struct diff_tempfile *temp)
+ struct diff_filespec *one,
+ struct diff_filespec *two)
{
- /* Use temp[i].name as input, name_a and name_b as labels */
int lc_a, lc_b;
- lc_a = count_lines(temp[0].name);
- lc_b = count_lines(temp[1].name);
+ 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);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
printf(" @@\n");
if (lc_a)
- copy_file('-', temp[0].name);
+ copy_file('-', one->data, one->size);
if (lc_b)
- copy_file('+', temp[1].name);
+ copy_file('+', two->data, two->size);
}
-static void builtin_diff(const char *name_a,
- const char *name_b,
- struct diff_tempfile *temp,
- const char *xfrm_msg,
- int complete_rewrite)
+static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
{
- int i, next_at, cmd_size;
- const char *const diff_cmd = "diff -L%s -L%s";
- const char *const diff_arg = "-- %s %s||:"; /* "||:" is to return 0 */
- const char *input_name_sq[2];
- const char *label_path[2];
- char *cmd;
-
- /* diff_cmd and diff_arg have 4 %s in total which makes
- * the sum of these strings 8 bytes larger than required.
- * we use 2 spaces around diff-opts, and we need to count
- * terminating NUL; we used to subtract 5 here, but we do not
- * care about small leaks in this subprocess that is about
- * to exec "diff" anymore.
+ if (!DIFF_FILE_VALID(one)) {
+ mf->ptr = (char *)""; /* does not matter */
+ mf->size = 0;
+ return 0;
+ }
+ else if (diff_populate_filespec(one, 0))
+ return -1;
+ mf->ptr = one->data;
+ mf->size = one->size;
+ return 0;
+}
+
+struct emit_callback {
+ struct xdiff_emit_state xm;
+ int nparents, color_diff;
+ const char **label_path;
+};
+
+const char *diff_get_color(int diff_use_color, enum color_diff ix)
+{
+ if (diff_use_color)
+ return diff_colors[ix];
+ return "";
+}
+
+static void fn_out_consume(void *priv, char *line, unsigned long len)
+{
+ int i;
+ struct emit_callback *ecbdata = priv;
+ const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+
+ 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);
+ ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
+ }
+
+ /* This is not really necessary for now because
+ * this codepath only deals with two-way diffs.
+ */
+ for (i = 0; i < len && line[i] == '@'; i++)
+ ;
+ if (2 <= i && i < len && line[i] == ' ') {
+ ecbdata->nparents = i - 1;
+ set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ }
+ else if (len < ecbdata->nparents)
+ set = reset;
+ else {
+ int nparents = ecbdata->nparents;
+ int color = DIFF_PLAIN;
+ for (i = 0; i < nparents && len; i++) {
+ if (line[i] == '-')
+ color = DIFF_FILE_OLD;
+ else if (line[i] == '+')
+ color = DIFF_FILE_NEW;
+ }
+ set = diff_get_color(ecbdata->color_diff, color);
+ }
+ if (len > 0 && line[len-1] == '\n')
+ len--;
+ fputs (set, stdout);
+ fwrite (line, len, 1, stdout);
+ puts (reset);
+}
+
+static char *pprint_rename(const char *a, const char *b)
+{
+ const char *old = a;
+ const char *new = b;
+ char *name = NULL;
+ int pfx_length, sfx_length;
+ int len_a = strlen(a);
+ int len_b = strlen(b);
+
+ /* Find common prefix */
+ pfx_length = 0;
+ while (*old && *new && *old == *new) {
+ if (*old == '/')
+ pfx_length = old - a + 1;
+ old++;
+ new++;
+ }
+
+ /* Find common suffix */
+ old = a + len_a;
+ new = b + len_b;
+ sfx_length = 0;
+ while (a <= old && b <= new && *old == *new) {
+ if (*old == '/')
+ sfx_length = len_a - (old - a);
+ old--;
+ new--;
+ }
+
+ /*
+ * pfx{mid-a => mid-b}sfx
+ * {pfx-a => pfx-b}sfx
+ * pfx{sfx-a => sfx-b}
+ * name-a => name-b
*/
- cmd_size = (strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg)
- + 128);
-
- for (i = 0; i < 2; i++) {
- input_name_sq[i] = sq_quote(temp[i].name);
- if (!strcmp(temp[i].name, "/dev/null"))
- label_path[i] = "/dev/null";
- else if (!i)
- label_path[i] = sq_quote(quote_two("a/", name_a));
+ if (pfx_length + sfx_length) {
+ int a_midlen = len_a - pfx_length - sfx_length;
+ int b_midlen = len_b - pfx_length - sfx_length;
+ if (a_midlen < 0) a_midlen = 0;
+ if (b_midlen < 0) b_midlen = 0;
+
+ name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7);
+ sprintf(name, "%.*s{%.*s => %.*s}%s",
+ pfx_length, a,
+ a_midlen, a + pfx_length,
+ b_midlen, b + pfx_length,
+ a + len_a - sfx_length);
+ }
+ else {
+ name = xmalloc(len_a + len_b + 5);
+ sprintf(name, "%s => %s", a, b);
+ }
+ return name;
+}
+
+struct diffstat_t {
+ struct xdiff_emit_state xm;
+
+ int nr;
+ int alloc;
+ struct diffstat_file {
+ char *name;
+ unsigned is_unmerged:1;
+ unsigned is_binary:1;
+ unsigned is_renamed:1;
+ unsigned int added, deleted;
+ } **files;
+};
+
+static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
+ const char *name_a,
+ const char *name_b)
+{
+ struct diffstat_file *x;
+ x = xcalloc(sizeof (*x), 1);
+ if (diffstat->nr == diffstat->alloc) {
+ diffstat->alloc = alloc_nr(diffstat->alloc);
+ diffstat->files = xrealloc(diffstat->files,
+ diffstat->alloc * sizeof(x));
+ }
+ diffstat->files[diffstat->nr++] = x;
+ if (name_b) {
+ x->name = pprint_rename(name_a, name_b);
+ x->is_renamed = 1;
+ }
+ else
+ x->name = strdup(name_a);
+ return x;
+}
+
+static void diffstat_consume(void *priv, char *line, unsigned long len)
+{
+ struct diffstat_t *diffstat = priv;
+ struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+
+ if (line[0] == '+')
+ x->added++;
+ else if (line[0] == '-')
+ x->deleted++;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+const char mime_boundary_leader[] = "------------";
+
+static void show_stats(struct diffstat_t* data)
+{
+ int i, len, add, del, total, adds = 0, dels = 0;
+ int max, max_change = 0, max_len = 0;
+ int total_files = data->nr;
+
+ if (data->nr == 0)
+ return;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ len = strlen(file->name);
+ if (max_len < len)
+ max_len = len;
+
+ if (file->is_binary || file->is_unmerged)
+ continue;
+ if (max_change < file->added + file->deleted)
+ max_change = file->added + file->deleted;
+ }
+
+ 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;
+ }
+
+ /*
+ * "scale" the filename
+ */
+ len = strlen(name);
+ max = max_len;
+ if (max > 50)
+ max = 50;
+ if (len > max) {
+ char *slash;
+ prefix = "...";
+ max -= 3;
+ name += len - max;
+ 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);
+ goto free_diffstat_file;
+ }
+ else if (data->files[i]->is_unmerged) {
+ printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ goto free_diffstat_file;
+ }
+ else if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ goto free_diffstat_file;
+ }
+
+ 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;
+ }
+ printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+ len, name, added + deleted,
+ add, pluses, del, minuses);
+ free_diffstat_file:
+ 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);
+}
+
+struct checkdiff_t {
+ struct xdiff_emit_state xm;
+ const char *filename;
+ int lineno;
+};
+
+static void checkdiff_consume(void *priv, char *line, unsigned long len)
+{
+ struct checkdiff_t *data = priv;
+
+ if (line[0] == '+') {
+ int i, spaces = 0;
+
+ data->lineno++;
+
+ /* 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);
+
+ /* 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);
+ } else if (line[0] == ' ')
+ data->lineno++;
+ else if (line[0] == '@') {
+ char *plus = strchr(line, '+');
+ if (plus)
+ data->lineno = strtol(plus, NULL, 10);
else
- label_path[i] = sq_quote(quote_two("b/", name_b));
- cmd_size += (strlen(label_path[i]) + strlen(input_name_sq[i]));
+ die("invalid diff");
}
+}
- cmd = xmalloc(cmd_size);
+static unsigned char *deflate_it(char *data,
+ unsigned long size,
+ unsigned long *result_size)
+{
+ int bound;
+ unsigned char *deflated;
+ z_stream stream;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, zlib_compression_level);
+ bound = deflateBound(&stream, size);
+ deflated = xmalloc(bound);
+ stream.next_out = deflated;
+ stream.avail_out = bound;
+
+ stream.next_in = (unsigned char *)data;
+ stream.avail_in = size;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ deflateEnd(&stream);
+ *result_size = stream.total_out;
+ return deflated;
+}
- next_at = 0;
- next_at += snprintf(cmd+next_at, cmd_size-next_at,
- diff_cmd, label_path[0], label_path[1]);
- next_at += snprintf(cmd+next_at, cmd_size-next_at,
- " %s ", diff_opts);
- next_at += snprintf(cmd+next_at, cmd_size-next_at,
- diff_arg, input_name_sq[0], input_name_sq[1]);
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+ void *cp;
+ void *delta;
+ void *deflated;
+ void *data;
+ unsigned long orig_size;
+ unsigned long delta_size;
+ unsigned long deflate_size;
+ unsigned long data_size;
+
+ printf("GIT binary patch\n");
+ /* We could do deflated delta, or we could do just deflated two,
+ * whichever is smaller.
+ */
+ delta = NULL;
+ deflated = deflate_it(two->ptr, two->size, &deflate_size);
+ if (one->size && two->size) {
+ delta = diff_delta(one->ptr, one->size,
+ two->ptr, two->size,
+ &delta_size, deflate_size);
+ if (delta) {
+ void *to_free = delta;
+ orig_size = delta_size;
+ delta = deflate_it(delta, delta_size, &delta_size);
+ free(to_free);
+ }
+ }
- printf("diff --git %s %s\n",
- quote_two("a/", name_a), quote_two("b/", name_b));
- if (label_path[0][0] == '/') {
- /* dev/null */
- printf("new file mode %s\n", temp[1].mode);
+ if (delta && delta_size < deflate_size) {
+ printf("delta %lu\n", orig_size);
+ free(deflated);
+ data = delta;
+ data_size = delta_size;
+ }
+ else {
+ printf("literal %lu\n", two->size);
+ free(delta);
+ data = deflated;
+ data_size = deflate_size;
+ }
+
+ /* emit data encoded in base85 */
+ cp = data;
+ while (data_size) {
+ int bytes = (52 < data_size) ? 52 : data_size;
+ char line[70];
+ data_size -= bytes;
+ if (bytes <= 26)
+ line[0] = bytes + 'A' - 1;
+ else
+ line[0] = bytes - 26 + 'a' - 1;
+ encode_85(line + 1, cp, bytes);
+ cp = (char *) cp + bytes;
+ puts(line);
+ }
+ printf("\n");
+ free(data);
+}
+
+#define FIRST_FEW_BYTES 8000
+static int mmfile_is_binary(mmfile_t *mf)
+{
+ long sz = mf->size;
+ if (FIRST_FEW_BYTES < sz)
+ sz = FIRST_FEW_BYTES;
+ if (memchr(mf->ptr, 0, sz))
+ return 1;
+ return 0;
+}
+
+static void builtin_diff(const char *name_a,
+ const char *name_b,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ struct diff_options *o,
+ int complete_rewrite)
+{
+ mmfile_t mf1, mf2;
+ const char *lbl[2];
+ char *a_one, *b_two;
+ 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);
+ 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 (lbl[0][0] == '/') {
+ /* /dev/null */
+ printf("%snew file mode %06o%s\n", set, two->mode, reset);
if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
+ printf("%s%s%s\n", set, xfrm_msg, reset);
}
- else if (label_path[1][0] == '/') {
- printf("deleted file mode %s\n", temp[0].mode);
+ else if (lbl[1][0] == '/') {
+ printf("%sdeleted file mode %06o%s\n", set, one->mode, reset);
if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
+ printf("%s%s%s\n", set, xfrm_msg, reset);
}
else {
- if (strcmp(temp[0].mode, temp[1].mode)) {
- printf("old mode %s\n", temp[0].mode);
- printf("new mode %s\n", temp[1].mode);
+ if (one->mode != two->mode) {
+ printf("%sold mode %06o%s\n", set, one->mode, reset);
+ printf("%snew mode %06o%s\n", set, two->mode, reset);
}
if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
- if (strncmp(temp[0].mode, temp[1].mode, 3))
- /* we do not run diff between different kind
- * of objects.
- */
- exit(0);
+ printf("%s%s%s\n", set, xfrm_msg, reset);
+ /*
+ * we do not run diff between different kind
+ * of objects.
+ */
+ if ((one->mode ^ two->mode) & S_IFMT)
+ goto free_ab_and_return;
if (complete_rewrite) {
- fflush(NULL);
- emit_rewrite_diff(name_a, name_b, temp);
- exit(0);
+ emit_rewrite_diff(name_a, name_b, one, two);
+ goto free_ab_and_return;
}
}
- fflush(NULL);
- execlp("/bin/sh","sh", "-c", cmd, NULL);
+
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
+ if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
+ /* Quite common confusing case */
+ if (mf1.size == mf2.size &&
+ !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+ goto free_ab_and_return;
+ if (o->binary)
+ emit_binary_diff(&mf1, &mf2);
+ else
+ printf("Binary files %s and %s differ\n",
+ lbl[0], lbl[1]);
+ }
+ else {
+ /* Crazy xdl interfaces.. */
+ const char *diffopts = getenv("GIT_DIFF_OPTS");
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+ struct emit_callback ecbdata;
+
+ memset(&ecbdata, 0, sizeof(ecbdata));
+ ecbdata.label_path = lbl;
+ ecbdata.color_diff = o->color_diff;
+ 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))
+ xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
+ else if (!strncmp(diffopts, "-u", 2))
+ xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
+ ecb.outf = xdiff_outf;
+ ecb.priv = &ecbdata;
+ ecbdata.xm.consume = fn_out_consume;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
+
+ free_ab_and_return:
+ free(a_one);
+ free(b_two);
+ return;
+}
+
+static void builtin_diffstat(const char *name_a, const char *name_b,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ struct diffstat_t *diffstat,
+ struct diff_options *o,
+ int complete_rewrite)
+{
+ mmfile_t mf1, mf2;
+ struct diffstat_file *data;
+
+ data = diffstat_add(diffstat, name_a, name_b);
+
+ if (!one || !two) {
+ data->is_unmerged = 1;
+ return;
+ }
+ if (complete_rewrite) {
+ diff_populate_filespec(one, 0);
+ diff_populate_filespec(two, 0);
+ data->deleted = count_lines(one->data, one->size);
+ data->added = count_lines(two->data, two->size);
+ return;
+ }
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
+ if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+ data->is_binary = 1;
+ else {
+ /* Crazy xdl interfaces.. */
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
+ xecfg.ctxlen = 0;
+ xecfg.flags = 0;
+ ecb.outf = xdiff_outf;
+ ecb.priv = diffstat;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
+}
+
+static void builtin_checkdiff(const char *name_a, const char *name_b,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ mmfile_t mf1, mf2;
+ struct checkdiff_t data;
+
+ if (!two)
+ return;
+
+ memset(&data, 0, sizeof(data));
+ data.xm.consume = checkdiff_consume;
+ data.filename = name_b ? name_b : name_a;
+ data.lineno = 0;
+
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
+ if (mmfile_is_binary(&mf2))
+ return;
+ else {
+ /* Crazy xdl interfaces.. */
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 0;
+ xecfg.flags = 0;
+ ecb.outf = xdiff_outf;
+ ecb.priv = &data;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
}
struct diff_filespec *alloc_filespec(const char *path)
unsigned short mode)
{
if (mode) {
- spec->mode = DIFF_FILE_CANON_MODE(mode);
+ spec->mode = canon_mode(mode);
memcpy(spec->sha1, sha1, 20);
spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
}
err_empty:
err = -1;
empty:
- s->data = "";
+ s->data = (char *)"";
s->size = 0;
return err;
}
munmap(s->data, s->size);
s->should_free = s->should_munmap = 0;
s->data = NULL;
+ free(s->cnt_data);
+ s->cnt_data = NULL;
}
static void prep_temp_blob(struct diff_tempfile *temp,
raise(signo);
}
+static int spawn_prog(const char *pgm, const char **arg)
+{
+ pid_t pid;
+ int status;
+
+ fflush(NULL);
+ pid = fork();
+ if (pid < 0)
+ die("unable to fork");
+ if (!pid) {
+ execvp(pgm, (char *const*) arg);
+ exit(255);
+ }
+
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+
+ /* Earlier we did not check the exit status because
+ * diff exits non-zero if files are different, and
+ * we are not interested in knowing that. It was a
+ * mistake which made it harder to quit a diff-*
+ * session that uses the git-apply-patch-script as
+ * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
+ * should also exit non-zero only when it wants to
+ * abort the entire diff-* session.
+ */
+ if (WIFEXITED(status) && !WEXITSTATUS(status))
+ return 0;
+ return -1;
+}
+
/* An external diff command takes:
*
* diff-cmd name infile1 infile1-sha1 infile1-mode \
const char *xfrm_msg,
int complete_rewrite)
{
+ const char *spawn_arg[10];
struct diff_tempfile *temp = diff_temp;
- pid_t pid;
- int status;
+ int retval;
static int atexit_asked = 0;
const char *othername;
+ const char **arg = &spawn_arg[0];
othername = (other? other : name);
if (one && two) {
signal(SIGINT, remove_tempfile_on_signal);
}
- fflush(NULL);
- pid = fork();
- if (pid < 0)
- die("unable to fork");
- if (!pid) {
- if (pgm) {
- if (one && two) {
- const char *exec_arg[10];
- const char **arg = &exec_arg[0];
- *arg++ = pgm;
- *arg++ = name;
- *arg++ = temp[0].name;
- *arg++ = temp[0].hex;
- *arg++ = temp[0].mode;
- *arg++ = temp[1].name;
- *arg++ = temp[1].hex;
- *arg++ = temp[1].mode;
- if (other) {
- *arg++ = other;
- *arg++ = xfrm_msg;
- }
- *arg = NULL;
- execvp(pgm, (char *const*) exec_arg);
- }
- else
- execlp(pgm, pgm, name, NULL);
+ if (one && two) {
+ *arg++ = pgm;
+ *arg++ = name;
+ *arg++ = temp[0].name;
+ *arg++ = temp[0].hex;
+ *arg++ = temp[0].mode;
+ *arg++ = temp[1].name;
+ *arg++ = temp[1].hex;
+ *arg++ = temp[1].mode;
+ if (other) {
+ *arg++ = other;
+ *arg++ = xfrm_msg;
}
- /*
- * otherwise we use the built-in one.
- */
- if (one && two)
- builtin_diff(name, othername, temp, xfrm_msg,
- complete_rewrite);
- else
- printf("* Unmerged path %s\n", name);
- exit(0);
- }
- if (waitpid(pid, &status, 0) < 0 ||
- !WIFEXITED(status) || WEXITSTATUS(status)) {
- /* Earlier we did not check the exit status because
- * diff exits non-zero if files are different, and
- * we are not interested in knowing that. It was a
- * mistake which made it harder to quit a diff-*
- * session that uses the git-apply-patch-script as
- * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
- * should also exit non-zero only when it wants to
- * abort the entire diff-* session.
- */
- remove_tempfile();
+ } else {
+ *arg++ = pgm;
+ *arg++ = name;
+ }
+ *arg = NULL;
+ retval = spawn_prog(pgm, spawn_arg);
+ remove_tempfile();
+ if (retval) {
fprintf(stderr, "external diff died, stopping at %s.\n", name);
exit(1);
}
- remove_tempfile();
+}
+
+static void run_diff_cmd(const char *pgm,
+ const char *name,
+ const char *other,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ struct diff_options *o,
+ int complete_rewrite)
+{
+ if (pgm) {
+ run_external_diff(pgm, name, other, one, two, xfrm_msg,
+ complete_rewrite);
+ return;
+ }
+ if (one && two)
+ builtin_diff(name, other ? other : name,
+ one, two, xfrm_msg, o, complete_rewrite);
+ else
+ printf("* Unmerged path %s\n", name);
}
static void diff_fill_sha1_info(struct diff_filespec *one)
if (DIFF_PAIR_UNMERGED(p)) {
/* unmerged */
- run_external_diff(pgm, p->one->path, NULL, NULL, NULL, NULL,
- 0);
+ run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
return;
}
}
if (memcmp(one->sha1, two->sha1, 20)) {
- char one_sha1[41];
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
- memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
len += snprintf(msg + len, sizeof(msg) - len,
"index %.*s..%.*s",
- abbrev, one_sha1, abbrev,
- sha1_to_hex(two->sha1));
+ abbrev, sha1_to_hex(one->sha1),
+ abbrev, sha1_to_hex(two->sha1));
if (one->mode == two->mode)
len += snprintf(msg + len, sizeof(msg) - len,
" %06o", one->mode);
* needs to be split into deletion and creation.
*/
struct diff_filespec *null = alloc_filespec(two->path);
- run_external_diff(NULL, name, other, one, null, xfrm_msg, 0);
+ run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
free(null);
null = alloc_filespec(one->path);
- run_external_diff(NULL, name, other, null, two, xfrm_msg, 0);
+ run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
free(null);
}
else
- run_external_diff(pgm, name, other, one, two, xfrm_msg,
- complete_rewrite);
+ run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
+ complete_rewrite);
free(name_munged);
free(other_munged);
}
+static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
+ struct diffstat_t *diffstat)
+{
+ const char *name;
+ const char *other;
+ int complete_rewrite = 0;
+
+ if (DIFF_PAIR_UNMERGED(p)) {
+ /* unmerged */
+ builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0);
+ return;
+ }
+
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+ diff_fill_sha1_info(p->one);
+ diff_fill_sha1_info(p->two);
+
+ if (p->status == DIFF_STATUS_MODIFIED && p->score)
+ complete_rewrite = 1;
+ builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite);
+}
+
+static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
+{
+ const char *name;
+ const char *other;
+
+ if (DIFF_PAIR_UNMERGED(p)) {
+ /* unmerged */
+ return;
+ }
+
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+ diff_fill_sha1_info(p->one);
+ diff_fill_sha1_info(p->two);
+
+ builtin_checkdiff(name, other, p->one, p->two);
+}
+
void diff_setup(struct diff_options *options)
{
memset(options, 0, sizeof(*options));
- options->output_format = DIFF_FORMAT_RAW;
options->line_termination = '\n';
options->break_opt = -1;
options->rename_limit = -1;
+ options->context = 3;
+ options->msg_sep = "";
options->change = diff_change;
options->add_remove = diff_addremove;
+ options->color_diff = diff_use_color_default;
+ options->detect_rename = diff_detect_rename_default;
}
int diff_setup_done(struct diff_options *options)
{
- if ((options->find_copies_harder &&
- options->detect_rename != DIFF_DETECT_COPY) ||
- (0 <= options->rename_limit && !options->detect_rename))
- return -1;
+ if (options->find_copies_harder)
+ options->detect_rename = DIFF_DETECT_COPY;
+
+ if (options->output_format & (DIFF_FORMAT_NAME |
+ DIFF_FORMAT_NAME_STATUS |
+ DIFF_FORMAT_CHECKDIFF |
+ DIFF_FORMAT_NO_OUTPUT))
+ options->output_format &= ~(DIFF_FORMAT_RAW |
+ DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_SUMMARY |
+ DIFF_FORMAT_PATCH);
+
+ /*
+ * These cases always need recursive; we do not drop caller-supplied
+ * recursive bits for other formats here.
+ */
+ if (options->output_format & (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_CHECKDIFF))
+ options->recursive = 1;
+ /*
+ * Also pickaxe would not work very well if you do not say recursive
+ */
+ if (options->pickaxe)
+ options->recursive = 1;
+
if (options->detect_rename && options->rename_limit < 0)
options->rename_limit = diff_rename_limit_default;
if (options->setup & DIFF_SETUP_USE_CACHE) {
return 0;
}
+static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
+{
+ char c, *eq;
+ int len;
+
+ if (*arg != '-')
+ return 0;
+ c = *++arg;
+ if (!c)
+ return 0;
+ if (c == arg_short) {
+ c = *++arg;
+ if (!c)
+ return 1;
+ if (val && isdigit(c)) {
+ char *end;
+ int n = strtoul(arg, &end, 10);
+ if (*end)
+ return 0;
+ *val = n;
+ return 1;
+ }
+ return 0;
+ }
+ if (c != '-')
+ return 0;
+ arg++;
+ eq = strchr(arg, '=');
+ if (eq)
+ len = eq - arg;
+ else
+ len = strlen(arg);
+ if (!len || strncmp(arg, arg_long, len))
+ return 0;
+ if (eq) {
+ int n;
+ char *end;
+ if (!isdigit(*++eq))
+ return 0;
+ n = strtoul(eq, &end, 10);
+ if (*end)
+ return 0;
+ *val = n;
+ }
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
- options->output_format = DIFF_FORMAT_PATCH;
+ options->output_format |= DIFF_FORMAT_PATCH;
+ else if (opt_arg(arg, 'U', "unified", &options->context))
+ options->output_format |= DIFF_FORMAT_PATCH;
+ else if (!strcmp(arg, "--raw"))
+ options->output_format |= DIFF_FORMAT_RAW;
+ else if (!strcmp(arg, "--patch-with-raw")) {
+ options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
+ }
+ else if (!strcmp(arg, "--stat"))
+ options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ else if (!strcmp(arg, "--check"))
+ options->output_format |= DIFF_FORMAT_CHECKDIFF;
+ else if (!strcmp(arg, "--summary"))
+ options->output_format |= DIFF_FORMAT_SUMMARY;
+ else if (!strcmp(arg, "--patch-with-stat")) {
+ options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
+ }
else if (!strcmp(arg, "-z"))
options->line_termination = 0;
else if (!strncmp(arg, "-l", 2))
options->rename_limit = strtoul(arg+2, NULL, 10);
else if (!strcmp(arg, "--full-index"))
options->full_index = 1;
+ else if (!strcmp(arg, "--binary")) {
+ options->output_format |= DIFF_FORMAT_PATCH;
+ options->full_index = options->binary = 1;
+ }
+ else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
+ options->text = 1;
+ }
else if (!strcmp(arg, "--name-only"))
- options->output_format = DIFF_FORMAT_NAME;
+ options->output_format |= DIFF_FORMAT_NAME;
else if (!strcmp(arg, "--name-status"))
- options->output_format = DIFF_FORMAT_NAME_STATUS;
+ options->output_format |= DIFF_FORMAT_NAME_STATUS;
else if (!strcmp(arg, "-R"))
options->reverse_diff = 1;
else if (!strncmp(arg, "-S", 2))
options->pickaxe = arg + 2;
- else if (!strcmp(arg, "-s"))
- options->output_format = DIFF_FORMAT_NO_OUTPUT;
+ else if (!strcmp(arg, "-s")) {
+ options->output_format |= DIFF_FORMAT_NO_OUTPUT;
+ }
else if (!strncmp(arg, "-O", 2))
options->orderfile = arg + 2;
else if (!strncmp(arg, "--diff-filter=", 14))
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)) {
if ((options->break_opt =
diff_scoreopt_parse(arg)) == -1)
else if (40 < options->abbrev)
options->abbrev = 40;
}
+ else if (!strcmp(arg, "--color"))
+ options->color_diff = 1;
+ else if (!strcmp(arg, "--no-color"))
+ options->color_diff = 0;
+ else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
+ 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, "--no-renames"))
+ options->detect_rename = 0;
else
return 0;
return 1;
struct diff_filespec *one,
struct diff_filespec *two)
{
- struct diff_filepair *dp = xmalloc(sizeof(*dp));
+ struct diff_filepair *dp = xcalloc(1, sizeof(*dp));
dp->one = one;
dp->two = two;
- dp->score = 0;
- dp->status = 0;
- dp->source_stays = 0;
- dp->broken_pair = 0;
if (queue)
diff_q(queue, dp);
return dp;
}
/* This is different from find_unique_abbrev() in that
- * it needs to deal with 0{40} SHA1.
+ * it stuffs the result with dots for alignment.
*/
const char *diff_unique_abbrev(const unsigned char *sha1, int len)
{
return sha1_to_hex(sha1);
abbrev = find_unique_abbrev(sha1, len);
- if (!abbrev) {
- if (!memcmp(sha1, null_sha1, 20)) {
- char *buf = sha1_to_hex(null_sha1);
- if (len < 37)
- strcpy(buf + len, "...");
- return buf;
- }
- else
- return sha1_to_hex(sha1);
- }
+ if (!abbrev)
+ return sha1_to_hex(sha1);
abblen = strlen(abbrev);
if (abblen < 37) {
static char hex[41];
}
static void diff_flush_raw(struct diff_filepair *p,
- int line_termination,
- int inter_name_termination,
struct diff_options *options)
{
int two_paths;
char status[10];
int abbrev = options->abbrev;
const char *path_one, *path_two;
- int output_format = options->output_format;
+ int inter_name_termination = '\t';
+ int line_termination = options->line_termination;
+
+ if (!line_termination)
+ inter_name_termination = 0;
path_one = p->one->path;
path_two = p->two->path;
two_paths = 0;
break;
}
- if (output_format != DIFF_FORMAT_NAME_STATUS) {
+ if (!(options->output_format & DIFF_FORMAT_NAME_STATUS)) {
printf(":%06o %06o %s ",
p->one->mode, p->two->mode,
diff_unique_abbrev(p->one->sha1, abbrev));
free((void*)path_two);
}
-static void diff_flush_name(struct diff_filepair *p,
- int inter_name_termination,
- int line_termination)
+static void diff_flush_name(struct diff_filepair *p, int line_termination)
{
char *path = p->two->path;
if (line_termination)
path = quote_one(p->two->path);
- else
- path = p->two->path;
printf("%s%c", path, line_termination);
if (p->two->path != path)
free(path);
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return; /* no tree diffs in patch format */
run_diff(p, o);
}
+static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
+ struct diffstat_t *diffstat)
+{
+ if (diff_unmodified_pair(p))
+ return;
+
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ return; /* no tree diffs in patch format */
+
+ run_diffstat(p, o, diffstat);
+}
+
+static void diff_flush_checkdiff(struct diff_filepair *p,
+ struct diff_options *o)
+{
+ if (diff_unmodified_pair(p))
+ return;
+
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ return; /* no tree diffs in patch format */
+
+ run_checkdiff(p, o);
+}
+
int diff_queue_is_empty(void)
{
struct diff_queue_struct *q = &diff_queued_diff;
diff_debug_queue("resolve-rename-copy done", q);
}
-void diff_flush(struct diff_options *options)
+static int check_pair_status(struct diff_filepair *p)
+{
+ switch (p->status) {
+ case DIFF_STATUS_UNKNOWN:
+ return 0;
+ case 0:
+ die("internal error in diff-resolve-rename-copy");
+ default:
+ return 1;
+ }
+}
+
+static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
+{
+ int fmt = opt->output_format;
+
+ if (fmt & DIFF_FORMAT_CHECKDIFF)
+ diff_flush_checkdiff(p, opt);
+ 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);
+}
+
+static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
+{
+ if (fs->mode)
+ printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+ else
+ printf(" %s %s\n", newdelete, fs->path);
+}
+
+
+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)
+ printf(" mode change %06o => %06o %s\n",
+ p->one->mode, p->two->mode, p->two->path);
+ 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;
+
+ /* 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));
+ show_mode_change(p, 0);
+}
+
+static void diff_summary(struct diff_filepair *p)
+{
+ switch(p->status) {
+ case DIFF_STATUS_DELETED:
+ show_file_mode_name("delete", p->one);
+ break;
+ case DIFF_STATUS_ADDED:
+ show_file_mode_name("create", p->two);
+ break;
+ case DIFF_STATUS_COPIED:
+ show_rename_copy("copy", p);
+ break;
+ case DIFF_STATUS_RENAMED:
+ show_rename_copy("rename", p);
+ break;
+ default:
+ if (p->score) {
+ printf(" rewrite %s (%d%%)\n", p->two->path,
+ (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ show_mode_change(p, 0);
+ } else show_mode_change(p, 1);
+ break;
+ }
+}
+
+struct patch_id_t {
+ struct xdiff_emit_state xm;
+ SHA_CTX *ctx;
+ int patchlen;
+};
+
+static int remove_space(char *line, int len)
+{
+ int i;
+ char *dst = line;
+ unsigned char c;
+
+ for (i = 0; i < len; i++)
+ if (!isspace((c = line[i])))
+ *dst++ = c;
+
+ return dst - line;
+}
+
+static void patch_id_consume(void *priv, char *line, unsigned long len)
+{
+ struct patch_id_t *data = priv;
+ int new_len;
+
+ /* Ignore line numbers when computing the SHA1 of the patch */
+ if (!strncmp(line, "@@ -", 4))
+ return;
+
+ new_len = remove_space(line, len);
+
+ SHA1_Update(data->ctx, line, new_len);
+ data->patchlen += new_len;
+}
+
+/* returns 0 upon success, and writes result into sha1 */
+static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
- int inter_name_termination = '\t';
- int diff_output_format = options->output_format;
- int line_termination = options->line_termination;
+ SHA_CTX ctx;
+ struct patch_id_t data;
+ char buffer[PATH_MAX * 4 + 20];
- if (!line_termination)
- inter_name_termination = 0;
+ SHA1_Init(&ctx);
+ memset(&data, 0, sizeof(struct patch_id_t));
+ data.ctx = &ctx;
+ data.xm.consume = patch_id_consume;
for (i = 0; i < q->nr; i++) {
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+ mmfile_t mf1, mf2;
struct diff_filepair *p = q->queue[i];
- if ((diff_output_format == DIFF_FORMAT_NO_OUTPUT) ||
- (p->status == DIFF_STATUS_UNKNOWN))
- continue;
+ int len1, len2;
+
if (p->status == 0)
- die("internal error in diff-resolve-rename-copy");
- switch (diff_output_format) {
- case DIFF_FORMAT_PATCH:
- diff_flush_patch(p, options);
- break;
- case DIFF_FORMAT_RAW:
- case DIFF_FORMAT_NAME_STATUS:
- diff_flush_raw(p, line_termination,
- inter_name_termination,
- options);
- break;
- case DIFF_FORMAT_NAME:
- diff_flush_name(p,
- inter_name_termination,
- line_termination);
+ return error("internal diff status error");
+ if (p->status == DIFF_STATUS_UNKNOWN)
+ continue;
+ if (diff_unmodified_pair(p))
+ continue;
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ continue;
+ if (DIFF_PAIR_UNMERGED(p))
+ continue;
+
+ diff_fill_sha1_info(p->one);
+ diff_fill_sha1_info(p->two);
+ if (fill_mmfile(&mf1, p->one) < 0 ||
+ fill_mmfile(&mf2, p->two) < 0)
+ return error("unable to read files to diff");
+
+ /* Maybe hash p->two? into the patch id? */
+ if (mmfile_is_binary(&mf2))
+ continue;
+
+ len1 = remove_space(p->one->path, strlen(p->one->path));
+ len2 = remove_space(p->two->path, strlen(p->two->path));
+ if (p->one->mode == 0)
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "newfilemode%06o"
+ "---/dev/null"
+ "+++b/%.*s",
+ len1, p->one->path,
+ len2, p->two->path,
+ p->two->mode,
+ len2, p->two->path);
+ else if (p->two->mode == 0)
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "deletedfilemode%06o"
+ "---a/%.*s"
+ "+++/dev/null",
+ len1, p->one->path,
+ len2, p->two->path,
+ p->one->mode,
+ len1, p->one->path);
+ else
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "---a/%.*s"
+ "+++b/%.*s",
+ len1, p->one->path,
+ len2, p->two->path,
+ len1, p->one->path,
+ len2, p->two->path);
+ SHA1_Update(&ctx, buffer, len1);
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 3;
+ xecfg.flags = XDL_EMIT_FUNCNAMES;
+ ecb.outf = xdiff_outf;
+ ecb.priv = &data;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
+
+ SHA1_Final(sha1, &ctx);
+ return 0;
+}
+
+int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ int result = diff_get_patch_id(options, sha1);
+
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+
+ free(q->queue);
+ q->queue = NULL;
+ q->nr = q->alloc = 0;
+
+ return result;
+}
+
+static int is_summary_empty(const struct diff_queue_struct *q)
+{
+ int i;
+
+ for (i = 0; i < q->nr; i++) {
+ const struct diff_filepair *p = q->queue[i];
+
+ switch (p->status) {
+ case DIFF_STATUS_DELETED:
+ case DIFF_STATUS_ADDED:
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ return 0;
+ default:
+ if (p->score)
+ return 0;
+ if (p->one->mode && p->two->mode &&
+ p->one->mode != p->two->mode)
+ return 0;
break;
}
- diff_free_filepair(q->queue[i]);
}
+ return 1;
+}
+
+void diff_flush(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i, output_format = options->output_format;
+ int separator = 0;
+
+ /*
+ * Order: raw, stat, summary, patch
+ * or: name/name-status/checkdiff (other bits clear)
+ */
+ if (!q->nr)
+ goto free_queue;
+
+ if (output_format & (DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NAME |
+ DIFF_FORMAT_NAME_STATUS |
+ DIFF_FORMAT_CHECKDIFF)) {
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (check_pair_status(p))
+ flush_one_pair(p, options);
+ }
+ separator++;
+ }
+
+ if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ struct diffstat_t diffstat;
+
+ memset(&diffstat, 0, sizeof(struct diffstat_t));
+ diffstat.xm.consume = diffstat_consume;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (check_pair_status(p))
+ diff_flush_stat(p, options, &diffstat);
+ }
+ show_stats(&diffstat);
+ separator++;
+ }
+
+ if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
+ for (i = 0; i < q->nr; i++)
+ diff_summary(q->queue[i]);
+ separator++;
+ }
+
+ if (output_format & DIFF_FORMAT_PATCH) {
+ if (separator) {
+ if (options->stat_sep) {
+ /* attach patch instead of inline */
+ fputs(options->stat_sep, stdout);
+ } else {
+ putchar(options->line_termination);
+ }
+ }
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (check_pair_status(p))
+ diff_flush_patch(p, options);
+ }
+ }
+
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+free_queue:
free(q->queue);
q->queue = NULL;
q->nr = q->alloc = 0;
void diffcore_std(struct diff_options *options)
{
- if (options->paths && options->paths[0])
- diffcore_pathspec(options->paths);
if (options->break_opt != -1)
diffcore_break(options->break_opt);
if (options->detect_rename)