#include "quote.h"
#include "diff.h"
#include "diffcore.h"
+#include "delta.h"
#include "xdiff-interface.h"
static int use_size_cache;
return 0;
}
+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
+ */
+ if (pfx_length + sfx_length) {
+ name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+ sprintf(name, "%.*s{%.*s => %.*s}%s",
+ pfx_length, a,
+ len_a - pfx_length - sfx_length, a + pfx_length,
+ len_b - pfx_length - sfx_length, 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 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)
+ const char *name_a,
+ const char *name_b)
{
struct diffstat_file *x;
x = xcalloc(sizeof (*x), 1);
diffstat->alloc * sizeof(x));
}
diffstat->files[diffstat->nr++] = x;
- x->name = strdup(name);
+ if (name_b) {
+ x->name = pprint_rename(name_a, name_b);
+ x->is_renamed = 1;
+ }
+ else
+ x->name = strdup(name_a);
return x;
}
if (data->nr == 0)
return;
- printf("---\n");
-
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
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++) {
char *qname = xmalloc(len + 1);
quote_c_style(name, qname, NULL, 0);
free(name);
- name = qname;
+ data->files[i]->name = name = qname;
}
/*
if (max + len > 70)
max = 70 - len;
- if (added < 0) {
- /* binary file */
+ if (data->files[i]->is_binary) {
printf(" %s%-*s | Bin\n", prefix, len, name);
- continue;
- } else if (added + deleted == 0) {
+ 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--;
- continue;
+ goto free_diffstat_file;
}
add = added;
add = (add * max + max_change / 2) / max_change;
del = total - add;
}
- /* TODO: binary */
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
len, name, added + deleted,
add, pluses, del, minuses);
- free(name);
+ free_diffstat_file:
+ free(data->files[i]->name);
free(data->files[i]);
}
free(data->files);
total_files, adds, dels);
}
+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, Z_BEST_COMPRESSION);
+ 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;
+}
+
+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);
+ }
+ }
+
+ 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 += bytes;
+ puts(line);
+ }
+ printf("\n");
+ free(data);
+}
+
#define FIRST_FEW_BYTES 8000
static int mmfile_is_binary(mmfile_t *mf)
{
struct diff_filespec *one,
struct diff_filespec *two,
const char *xfrm_msg,
+ struct diff_options *o,
int complete_rewrite)
{
mmfile_t mf1, mf2;
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))
- printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
+ if (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");
}
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_filespec *one,
+ struct diff_filespec *two,
+ struct diffstat_t *diffstat,
+ int complete_rewrite)
{
mmfile_t mf1, mf2;
struct diffstat_file *data;
- data = diffstat_add(diffstat, name_a ? name_a : name_b);
+ 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->added = -1;
+ data->is_binary = 1;
else {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitcb_t ecb;
xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 3;
- xecfg.flags = XDL_EMIT_FUNCNAMES;
+ xecfg.ctxlen = 0;
+ xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = diffstat;
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
struct diff_filespec *one,
struct diff_filespec *two,
const char *xfrm_msg,
+ struct diff_options *o,
int complete_rewrite)
{
if (pgm) {
}
if (one && two)
builtin_diff(name, other ? other : name,
- one, two, xfrm_msg, complete_rewrite);
+ one, two, xfrm_msg, o, complete_rewrite);
else
printf("* Unmerged path %s\n", name);
}
if (DIFF_PAIR_UNMERGED(p)) {
/* unmerged */
- run_diff_cmd(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_diff_cmd(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_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
+ run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
free(null);
}
else
- run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
+ run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
complete_rewrite);
free(name_munged);
}
static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
- struct diffstat_t *diffstat)
+ 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);
+ builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
return;
}
diff_fill_sha1_info(p->one);
diff_fill_sha1_info(p->two);
- builtin_diffstat(name, other, p->one, p->two, diffstat);
+ if (p->status == DIFF_STATUS_MODIFIED && p->score)
+ complete_rewrite = 1;
+ builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
}
void diff_setup(struct diff_options *options)
options->detect_rename != DIFF_DETECT_COPY) ||
(0 <= options->rename_limit && !options->detect_rename))
return -1;
+
+ /*
+ * These cases always need recursive; we do not drop caller-supplied
+ * recursive bits for other formats here.
+ */
+ if ((options->output_format == DIFF_FORMAT_PATCH) ||
+ (options->output_format == DIFF_FORMAT_DIFFSTAT))
+ 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) {
}
else if (!strcmp(arg, "--stat"))
options->output_format = DIFF_FORMAT_DIFFSTAT;
+ else if (!strcmp(arg, "--patch-with-stat")) {
+ options->output_format = DIFF_FORMAT_PATCH;
+ options->with_stat = 1;
+ }
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, "--name-only"))
options->output_format = DIFF_FORMAT_NAME;
else if (!strcmp(arg, "--name-status"))
}
static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
- struct diffstat_t *diffstat)
+ struct diffstat_t *diffstat)
{
if (diff_unmodified_pair(p))
return;
int diff_output_format = options->output_format;
struct diffstat_t *diffstat = NULL;
- if (diff_output_format == DIFF_FORMAT_DIFFSTAT) {
+ if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
diffstat = xcalloc(sizeof (struct diffstat_t), 1);
diffstat->xm.consume = diffstat_consume;
}
}
putchar(options->line_termination);
}
+ if (options->with_stat) {
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
+ diffstat);
+ }
+ show_stats(diffstat);
+ free(diffstat);
+ diffstat = NULL;
+ putchar(options->line_termination);
+ }
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
flush_one_pair(p, diff_output_format, options, diffstat);