#include "diff.h"
#include "diffcore.h"
-struct linehash {
- unsigned long bytes;
- unsigned long hash;
+/*
+ * Idea here is very simple.
+ *
+ * Almost all data we are interested in are text, but sometimes we have
+ * to deal with binary data. So we cut them into chunks delimited by
+ * LF byte, or 64-byte sequence, whichever comes first, and hash them.
+ *
+ * For those chunks, if the source buffer has more instances of it
+ * than the destination buffer, that means the difference are the
+ * number of bytes not copied from source to destination. If the
+ * counts are the same, everything was copied from source to
+ * destination. If the destination has more, everything was copied,
+ * and destination added more.
+ *
+ * We are doing an approximation so we do not really have to waste
+ * memory by actually storing the sequence. We just hash them into
+ * somewhere around 2^16 hashbuckets and count the occurrences.
+ */
+
+/* Wild guess at the initial hash size */
+#define INITIAL_HASH_SIZE 9
+
+/* We leave more room in smaller hash but do not let it
+ * grow to have unused hole too much.
+ */
+#define INITIAL_FREE(sz_log2) ((1<<(sz_log2))*(sz_log2-3)/(sz_log2))
+
+/* A prime rather carefully chosen between 2^16..2^17, so that
+ * HASHBASE < INITIAL_FREE(17). We want to keep the maximum hashtable
+ * size under the current 2<<17 maximum, which can hold this many
+ * different values before overflowing to hashtable of size 2<<18.
+ */
+#define HASHBASE 107927
+
+struct spanhash {
+ unsigned int hashval;
+ unsigned int cnt;
+};
+struct spanhash_top {
+ int alloc_log2;
+ int free;
+ struct spanhash data[FLEX_ARRAY];
};
-static unsigned long hash_extended_line(const unsigned char **buf_p,
- unsigned long left)
+static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig)
{
- /* An extended line is zero or more whitespace letters (including LF)
- * followed by one non whitespace letter followed by zero or more
- * non LF, and terminated with by a LF (or EOF).
- */
- const unsigned char *bol = *buf_p;
- const unsigned char *buf = bol;
- unsigned long hashval = 0;
- while (left) {
- unsigned c = *buf++;
- if (!c)
- goto binary;
- left--;
- if (' ' < c) {
- hashval = c;
- break;
+ struct spanhash_top *new;
+ int i;
+ int osz = 1 << orig->alloc_log2;
+ int sz = osz << 1;
+
+ new = xmalloc(sizeof(*orig) + sizeof(struct spanhash) * sz);
+ new->alloc_log2 = orig->alloc_log2 + 1;
+ new->free = INITIAL_FREE(new->alloc_log2);
+ memset(new->data, 0, sizeof(struct spanhash) * sz);
+ for (i = 0; i < osz; i++) {
+ struct spanhash *o = &(orig->data[i]);
+ int bucket;
+ if (!o->cnt)
+ continue;
+ bucket = o->hashval & (sz - 1);
+ while (1) {
+ struct spanhash *h = &(new->data[bucket++]);
+ if (!h->cnt) {
+ h->hashval = o->hashval;
+ h->cnt = o->cnt;
+ new->free--;
+ break;
+ }
+ if (sz <= bucket)
+ bucket = 0;
}
}
- while (left) {
- unsigned c = *buf++;
- if (!c)
- goto binary;
- left--;
- if (c == '\n')
- break;
- if (' ' < c)
- hashval = hashval * 11 + c;
- }
- *buf_p = buf;
- return hashval;
-
- binary:
- *buf_p = NULL;
- return 0;
+ free(orig);
+ return new;
}
-static int linehash_compare(const void *a_, const void *b_)
+static struct spanhash_top *add_spanhash(struct spanhash_top *top,
+ unsigned int hashval, int cnt)
{
- struct linehash *a = (struct linehash *) a_;
- struct linehash *b = (struct linehash *) b_;
- if (a->hash < b->hash) return -1;
- if (a->hash > b->hash) return 1;
- return 0;
-}
+ int bucket, lim;
+ struct spanhash *h;
-static struct linehash *hash_lines(const unsigned char *buf,
- unsigned long size)
-{
- const unsigned char *eobuf = buf + size;
- struct linehash *line = NULL;
- int alloc = 0, used = 0;
-
- while (buf < eobuf) {
- const unsigned char *ptr = buf;
- unsigned long hash = hash_extended_line(&buf, eobuf-ptr);
- if (!buf) {
- free(line);
- return NULL;
+ lim = (1 << top->alloc_log2);
+ bucket = hashval & (lim - 1);
+ while (1) {
+ h = &(top->data[bucket++]);
+ if (!h->cnt) {
+ h->hashval = hashval;
+ h->cnt = cnt;
+ top->free--;
+ if (top->free < 0)
+ return spanhash_rehash(top);
+ return top;
}
- if (alloc <= used) {
- alloc = alloc_nr(alloc);
- line = xrealloc(line, sizeof(*line) * alloc);
+ if (h->hashval == hashval) {
+ h->cnt += cnt;
+ return top;
}
- line[used].bytes = buf - ptr;
- line[used].hash = hash;
- used++;
+ if (lim <= bucket)
+ bucket = 0;
}
- qsort(line, used, sizeof(*line), linehash_compare);
+}
- /* Terminate the list */
- if (alloc <= used)
- line = xrealloc(line, sizeof(*line) * (used+1));
- line[used].bytes = line[used].hash = 0;
- return line;
+static int spanhash_cmp(const void *a_, const void *b_)
+{
+ const struct spanhash *a = a_;
+ const struct spanhash *b = b_;
+
+ /* A count of zero compares at the end.. */
+ if (!a->cnt)
+ return !b->cnt ? 0 : 1;
+ if (!b->cnt)
+ return -1;
+ return a->hashval < b->hashval ? -1 :
+ a->hashval > b->hashval ? 1 : 0;
}
-int diffcore_count_changes(void *src, unsigned long src_size,
- void *dst, unsigned long dst_size,
+static struct spanhash_top *hash_chars(struct diff_filespec *one)
+{
+ int i, n;
+ unsigned int accum1, accum2, hashval;
+ struct spanhash_top *hash;
+ unsigned char *buf = one->data;
+ unsigned int sz = one->size;
+ int is_text = !diff_filespec_is_binary(one);
+
+ i = INITIAL_HASH_SIZE;
+ hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i));
+ hash->alloc_log2 = i;
+ hash->free = INITIAL_FREE(i);
+ memset(hash->data, 0, sizeof(struct spanhash) * (1<<i));
+
+ n = 0;
+ accum1 = accum2 = 0;
+ while (sz) {
+ unsigned int c = *buf++;
+ unsigned int old_1 = accum1;
+ sz--;
+
+ /* Ignore CR in CRLF sequence if text */
+ if (is_text && c == '\r' && sz && *buf == '\n')
+ continue;
+
+ accum1 = (accum1 << 7) ^ (accum2 >> 25);
+ accum2 = (accum2 << 7) ^ (old_1 >> 25);
+ accum1 += c;
+ if (++n < 64 && c != '\n')
+ continue;
+ hashval = (accum1 + accum2 * 0x61) % HASHBASE;
+ hash = add_spanhash(hash, hashval, n);
+ n = 0;
+ accum1 = accum2 = 0;
+ }
+ qsort(hash->data,
+ 1ul << hash->alloc_log2,
+ sizeof(hash->data[0]),
+ spanhash_cmp);
+ return hash;
+}
+
+int diffcore_count_changes(struct diff_filespec *src,
+ struct diff_filespec *dst,
+ void **src_count_p,
+ void **dst_count_p,
unsigned long delta_limit,
unsigned long *src_copied,
unsigned long *literal_added)
{
- struct linehash *src_lines, *dst_lines;
+ struct spanhash *s, *d;
+ struct spanhash_top *src_count, *dst_count;
unsigned long sc, la;
- src_lines = hash_lines(src, src_size);
- if (!src_lines)
- return -1;
- dst_lines = hash_lines(dst, dst_size);
- if (!dst_lines) {
- free(src_lines);
- return -1;
+ src_count = dst_count = NULL;
+ if (src_count_p)
+ src_count = *src_count_p;
+ if (!src_count) {
+ src_count = hash_chars(src);
+ if (src_count_p)
+ *src_count_p = src_count;
+ }
+ if (dst_count_p)
+ dst_count = *dst_count_p;
+ if (!dst_count) {
+ dst_count = hash_chars(dst);
+ if (dst_count_p)
+ *dst_count_p = dst_count;
}
sc = la = 0;
- while (src_lines->bytes && dst_lines->bytes) {
- int cmp = linehash_compare(src_lines, dst_lines);
- if (!cmp) {
- sc += src_lines->bytes;
- src_lines++;
- dst_lines++;
- continue;
+
+ s = src_count->data;
+ d = dst_count->data;
+ for (;;) {
+ unsigned dst_cnt, src_cnt;
+ if (!s->cnt)
+ break; /* we checked all in src */
+ while (d->cnt) {
+ if (d->hashval >= s->hashval)
+ break;
+ la += d->cnt;
+ d++;
}
- if (cmp < 0) {
- src_lines++;
- continue;
+ src_cnt = s->cnt;
+ dst_cnt = 0;
+ if (d->cnt && d->hashval == s->hashval) {
+ dst_cnt = d->cnt;
+ d++;
}
- la += dst_lines->bytes;
- dst_lines++;
+ if (src_cnt < dst_cnt) {
+ la += dst_cnt - src_cnt;
+ sc += src_cnt;
+ }
+ else
+ sc += dst_cnt;
+ s++;
}
- while (dst_lines->bytes) {
- la += dst_lines->bytes;
- dst_lines++;
+ while (d->cnt) {
+ la += d->cnt;
+ d++;
}
+
+ if (!src_count_p)
+ free(src_count);
+ if (!dst_count_p)
+ free(dst_count);
*src_copied = sc;
*literal_added = la;
return 0;