File Coverage

deps/libgit2/src/libgit2/hashsig.c
Criterion Covered Total %
statement 127 169 75.1
branch 70 122 57.3
condition n/a
subroutine n/a
pod n/a
total 197 291 67.7


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "common.h"
9              
10             #include "git2/sys/hashsig.h"
11             #include "futils.h"
12             #include "util.h"
13              
14             typedef uint32_t hashsig_t;
15             typedef uint64_t hashsig_state;
16              
17             #define HASHSIG_SCALE 100
18              
19             #define HASHSIG_MAX_RUN 80
20             #define HASHSIG_HASH_START INT64_C(0x012345678ABCDEF0)
21             #define HASHSIG_HASH_SHIFT 5
22              
23             #define HASHSIG_HASH_MIX(S,CH) \
24             (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH)
25              
26             #define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
27             #define HASHSIG_HEAP_MIN_SIZE 4
28              
29             typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
30              
31             typedef struct {
32             int size, asize;
33             hashsig_cmp cmp;
34             hashsig_t values[HASHSIG_HEAP_SIZE];
35             } hashsig_heap;
36              
37             struct git_hashsig {
38             hashsig_heap mins;
39             hashsig_heap maxs;
40             size_t lines;
41             git_hashsig_option_t opt;
42             };
43              
44             #define HEAP_LCHILD_OF(I) (((I)<<1)+1)
45             #define HEAP_RCHILD_OF(I) (((I)<<1)+2)
46             #define HEAP_PARENT_OF(I) (((I)-1)>>1)
47              
48 22           static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
49             {
50 22           h->size = 0;
51 22           h->asize = HASHSIG_HEAP_SIZE;
52 22           h->cmp = cmp;
53 22           }
54              
55 54           static int hashsig_cmp_max(const void *a, const void *b, void *payload)
56             {
57 54           hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
58 54           GIT_UNUSED(payload);
59 54 100         return (av < bv) ? -1 : (av > bv) ? 1 : 0;
60             }
61              
62 68           static int hashsig_cmp_min(const void *a, const void *b, void *payload)
63             {
64 68           hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
65 68           GIT_UNUSED(payload);
66 68 50         return (av > bv) ? -1 : (av < bv) ? 1 : 0;
67             }
68              
69 72           static void hashsig_heap_up(hashsig_heap *h, int el)
70             {
71 72           int parent_el = HEAP_PARENT_OF(el);
72              
73 74 100         while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {
    100          
74 2           hashsig_t t = h->values[el];
75 2           h->values[el] = h->values[parent_el];
76 2           h->values[parent_el] = t;
77              
78 2           el = parent_el;
79 2           parent_el = HEAP_PARENT_OF(el);
80             }
81 72           }
82              
83 0           static void hashsig_heap_down(hashsig_heap *h, int el)
84             {
85             hashsig_t v, lv, rv;
86              
87             /* 'el < h->size / 2' tests if el is bottom row of heap */
88              
89 0 0         while (el < h->size / 2) {
90 0           int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel;
91              
92 0           v = h->values[el];
93 0           lv = h->values[lel];
94 0           rv = h->values[rel];
95              
96 0 0         if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)
    0          
97 0           break;
98              
99 0 0         swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;
100              
101 0           h->values[el] = h->values[swapel];
102 0           h->values[swapel] = v;
103              
104 0           el = swapel;
105             }
106 0           }
107              
108 22           static void hashsig_heap_sort(hashsig_heap *h)
109             {
110             /* only need to do this at the end for signature comparison */
111 22           git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);
112 22           }
113              
114 72           static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
115             {
116             /* if heap is not full, insert new element */
117 72 50         if (h->size < h->asize) {
118 72           h->values[h->size++] = val;
119 72           hashsig_heap_up(h, h->size - 1);
120             }
121              
122             /* if heap is full, pop top if new element should replace it */
123 0 0         else if (h->cmp(&val, &h->values[0], NULL) > 0) {
124 0           h->size--;
125 0           h->values[0] = h->values[h->size];
126 0           hashsig_heap_down(h, 0);
127             }
128              
129 72           }
130              
131             typedef struct {
132             int use_ignores;
133             uint8_t ignore_ch[256];
134             } hashsig_in_progress;
135              
136 11           static int hashsig_in_progress_init(
137             hashsig_in_progress *prog, git_hashsig *sig)
138             {
139             int i;
140              
141             /* no more than one can be set */
142 11 100         GIT_ASSERT(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) ||
    50          
143             !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE));
144              
145 11 100         if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) {
146 514 100         for (i = 0; i < 256; ++i)
147 512           prog->ignore_ch[i] = git__isspace_nonlf(i);
148 2           prog->use_ignores = 1;
149 9 50         } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) {
150 2313 100         for (i = 0; i < 256; ++i)
151 2304           prog->ignore_ch[i] = git__isspace(i);
152 9           prog->use_ignores = 1;
153             } else {
154 0           memset(prog, 0, sizeof(*prog));
155             }
156              
157 11           return 0;
158             }
159              
160 11           static int hashsig_add_hashes(
161             git_hashsig *sig,
162             const uint8_t *data,
163             size_t size,
164             hashsig_in_progress *prog)
165             {
166 11           const uint8_t *scan = data, *end = data + size;
167 11           hashsig_state state = HASHSIG_HASH_START;
168 11           int use_ignores = prog->use_ignores, len;
169             uint8_t ch;
170              
171 50 100         while (scan < end) {
172 39           state = HASHSIG_HASH_START;
173              
174 579 100         for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) {
    50          
175 575           ch = *scan;
176              
177 575 100         if (use_ignores)
178 141 50         for (; scan < end && git__isspace_nonlf(ch); ch = *scan)
    100          
179 2           ++scan;
180 436 50         else if (sig->opt &
181             (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE))
182 436 50         for (; scan < end && ch == '\r'; ch = *scan)
    50          
183 0           ++scan;
184              
185             /* peek at next character to decide what to do next */
186 575 100         if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE)
187 465           use_ignores = (ch == '\n');
188              
189 575 50         if (scan >= end)
190 0           break;
191 575           ++scan;
192              
193             /* check run terminator */
194 575 100         if (ch == '\n' || ch == '\0') {
    50          
195 35           sig->lines++;
196 35           break;
197             }
198              
199 540           ++len;
200 540           HASHSIG_HASH_MIX(state, ch);
201             }
202              
203 39 100         if (len > 0) {
204 36           hashsig_heap_insert(&sig->mins, (hashsig_t)state);
205 36           hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
206              
207 36 100         while (scan < end && (*scan == '\n' || !*scan))
    50          
    50          
208 0           ++scan;
209             }
210             }
211              
212 11           prog->use_ignores = use_ignores;
213              
214 11           return 0;
215             }
216              
217 11           static int hashsig_finalize_hashes(git_hashsig *sig)
218             {
219 11 100         if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE &&
    50          
220 4           !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) {
221 0           git_error_set(GIT_ERROR_INVALID,
222             "file too small for similarity signature calculation");
223 0           return GIT_EBUFS;
224             }
225              
226 11           hashsig_heap_sort(&sig->mins);
227 11           hashsig_heap_sort(&sig->maxs);
228              
229 11           return 0;
230             }
231              
232 11           static git_hashsig *hashsig_alloc(git_hashsig_option_t opts)
233             {
234 11           git_hashsig *sig = git__calloc(1, sizeof(git_hashsig));
235 11 50         if (!sig)
236 0           return NULL;
237              
238 11           hashsig_heap_init(&sig->mins, hashsig_cmp_min);
239 11           hashsig_heap_init(&sig->maxs, hashsig_cmp_max);
240 11           sig->opt = opts;
241              
242 11           return sig;
243             }
244              
245 6           int git_hashsig_create(
246             git_hashsig **out,
247             const char *buf,
248             size_t buflen,
249             git_hashsig_option_t opts)
250             {
251             int error;
252             hashsig_in_progress prog;
253 6           git_hashsig *sig = hashsig_alloc(opts);
254 6 50         GIT_ERROR_CHECK_ALLOC(sig);
255              
256 6 50         if ((error = hashsig_in_progress_init(&prog, sig)) < 0)
257 0           return error;
258              
259 6           error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog);
260              
261 6 50         if (!error)
262 6           error = hashsig_finalize_hashes(sig);
263              
264 6 50         if (!error)
265 6           *out = sig;
266             else
267 0           git_hashsig_free(sig);
268              
269 6           return error;
270             }
271              
272 5           int git_hashsig_create_fromfile(
273             git_hashsig **out,
274             const char *path,
275             git_hashsig_option_t opts)
276             {
277             uint8_t buf[0x1000];
278 5           ssize_t buflen = 0;
279 5           int error = 0, fd;
280             hashsig_in_progress prog;
281 5           git_hashsig *sig = hashsig_alloc(opts);
282 5 50         GIT_ERROR_CHECK_ALLOC(sig);
283              
284 5 50         if ((fd = git_futils_open_ro(path)) < 0) {
285 0           git__free(sig);
286 0           return fd;
287             }
288              
289 5 50         if ((error = hashsig_in_progress_init(&prog, sig)) < 0) {
290 0           p_close(fd);
291 0           return error;
292             }
293              
294 10 50         while (!error) {
295 10 100         if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
296 5 50         if ((error = (int)buflen) < 0)
297 0           git_error_set(GIT_ERROR_OS,
298             "read error on '%s' calculating similarity hashes", path);
299 5           break;
300             }
301              
302 5           error = hashsig_add_hashes(sig, buf, buflen, &prog);
303             }
304              
305 5           p_close(fd);
306              
307 5 50         if (!error)
308 5           error = hashsig_finalize_hashes(sig);
309              
310 5 50         if (!error)
311 5           *out = sig;
312             else
313 0           git_hashsig_free(sig);
314              
315 5           return error;
316             }
317              
318 11           void git_hashsig_free(git_hashsig *sig)
319             {
320 11           git__free(sig);
321 11           }
322              
323 7           static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
324             {
325 7           int matches = 0, i, j, cmp;
326              
327 7 50         GIT_ASSERT_WITH_RETVAL(a->cmp == b->cmp, 0);
328              
329             /* hash heaps are sorted - just look for overlap vs total */
330              
331 25 100         for (i = 0, j = 0; i < a->size && j < b->size; ) {
    100          
332 18           cmp = a->cmp(&a->values[i], &b->values[j], NULL);
333              
334 18 50         if (cmp < 0)
335 0           ++i;
336 18 100         else if (cmp > 0)
337 6           ++j;
338             else {
339 12           ++i; ++j; ++matches;
340             }
341             }
342              
343 7           return HASHSIG_SCALE * (matches * 2) / (a->size + b->size);
344             }
345              
346 7           int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
347             {
348             /* if we have no elements in either file then each file is either
349             * empty or blank. if we're ignoring whitespace then the files are
350             * similar, otherwise they're dissimilar.
351             */
352 7 50         if (a->mins.size == 0 && b->mins.size == 0) {
    0          
353 0 0         if ((!a->lines && !b->lines) ||
    0          
    0          
354 0           (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE))
355 0           return HASHSIG_SCALE;
356             else
357 0           return 0;
358             }
359              
360             /* if we have fewer than the maximum number of elements, then just use
361             * one array since the two arrays will be the same
362             */
363 7 50         if (a->mins.size < HASHSIG_HEAP_SIZE) {
364 7           return hashsig_heap_compare(&a->mins, &b->mins);
365             } else {
366             int mins, maxs;
367              
368 0 0         if ((mins = hashsig_heap_compare(&a->mins, &b->mins)) < 0)
369 0           return mins;
370 0 0         if ((maxs = hashsig_heap_compare(&a->maxs, &b->maxs)) < 0)
371 0           return maxs;
372              
373 0           return (mins + maxs) / 2;
374             }
375             }