+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) {
+ 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(len_a + len_b - pfx_length - 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[]= "----------------------------------------------------------------------";
+
+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++) {
+ 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);
+}
+
+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);
+}
+