File Coverage

deps/libgit2/src/oid.c
Criterion Covered Total %
statement 89 202 44.0
branch 33 110 30.0
condition n/a
subroutine n/a
pod n/a
total 122 312 39.1


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 "oid.h"
9              
10             #include "git2/oid.h"
11             #include "repository.h"
12             #include "global.h"
13             #include
14             #include
15              
16             static char to_hex[] = "0123456789abcdef";
17              
18 19           static int oid_error_invalid(const char *msg)
19             {
20 19           git_error_set(GIT_ERROR_INVALID, "unable to parse OID - %s", msg);
21 19           return -1;
22             }
23              
24 1408           int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
25             {
26             size_t p;
27             int v;
28              
29 1408 50         assert(out && str);
    50          
30              
31 1408 50         if (!length)
32 0           return oid_error_invalid("too short");
33              
34 1408 50         if (length > GIT_OID_HEXSZ)
35 0           return oid_error_invalid("too long");
36              
37 1408           memset(out->id, 0, GIT_OID_RAWSZ);
38              
39 56360 100         for (p = 0; p < length; p++) {
40 54971           v = git__fromhex(str[p]);
41 54971 100         if (v < 0)
42 19           return oid_error_invalid("contains invalid characters");
43              
44 54952 100         out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4));
45             }
46              
47 1389           return 0;
48             }
49              
50 0           int git_oid_fromstrp(git_oid *out, const char *str)
51             {
52 0           return git_oid_fromstrn(out, str, strlen(str));
53             }
54              
55 1136           int git_oid_fromstr(git_oid *out, const char *str)
56             {
57 1136           return git_oid_fromstrn(out, str, GIT_OID_HEXSZ);
58             }
59              
60 83919           GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
61             {
62 83919           *str++ = to_hex[val >> 4];
63 83919           *str++ = to_hex[val & 0xf];
64 83919           return str;
65             }
66              
67 2672           int git_oid_nfmt(char *str, size_t n, const git_oid *oid)
68             {
69             size_t i, max_i;
70              
71 2672 100         if (!oid) {
72 1           memset(str, 0, n);
73 1           return 0;
74             }
75 2671 100         if (n > GIT_OID_HEXSZ) {
76 106           memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
77 106           n = GIT_OID_HEXSZ;
78             }
79              
80 2671           max_i = n / 2;
81              
82 54790 100         for (i = 0; i < max_i; i++)
83 52119           str = fmt_one(str, oid->id[i]);
84              
85 2671 100         if (n & 1)
86 51           *str++ = to_hex[oid->id[i] >> 4];
87              
88 2671           return 0;
89             }
90              
91 618           int git_oid_fmt(char *str, const git_oid *oid)
92             {
93 618           return git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
94             }
95              
96 1590           int git_oid_pathfmt(char *str, const git_oid *oid)
97             {
98             size_t i;
99              
100 1590           str = fmt_one(str, oid->id[0]);
101 1590           *str++ = '/';
102 31800 100         for (i = 1; i < sizeof(oid->id); i++)
103 30210           str = fmt_one(str, oid->id[i]);
104              
105 1590           return 0;
106             }
107              
108 20           char *git_oid_tostr_s(const git_oid *oid)
109             {
110 20           char *str = GIT_GLOBAL->oid_fmt;
111 20           git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
112 20           return str;
113             }
114              
115 7           char *git_oid_allocfmt(const git_oid *oid)
116             {
117 7           char *str = git__malloc(GIT_OID_HEXSZ + 1);
118 7 50         if (!str)
119 0           return NULL;
120 7           git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
121 7           return str;
122             }
123              
124 1948           char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
125             {
126 1948 50         if (!out || n == 0)
    50          
127 0           return "";
128              
129 1948 50         if (n > GIT_OID_HEXSZ + 1)
130 0           n = GIT_OID_HEXSZ + 1;
131              
132 1948           git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
133 1948           out[n - 1] = '\0';
134              
135 1948           return out;
136             }
137              
138 586           int git_oid__parse(
139             git_oid *oid, const char **buffer_out,
140             const char *buffer_end, const char *header)
141             {
142 586           const size_t sha_len = GIT_OID_HEXSZ;
143 586           const size_t header_len = strlen(header);
144              
145 586           const char *buffer = *buffer_out;
146              
147 586 50         if (buffer + (header_len + sha_len + 1) > buffer_end)
148 0           return -1;
149              
150 586 100         if (memcmp(buffer, header, header_len) != 0)
151 268           return -1;
152              
153 318 50         if (buffer[header_len + sha_len] != '\n')
154 0           return -1;
155              
156 318 50         if (git_oid_fromstr(oid, buffer + header_len) < 0)
157 0           return -1;
158              
159 318           *buffer_out = buffer + (header_len + sha_len + 1);
160              
161 318           return 0;
162             }
163              
164 112           void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid)
165             {
166             char hex_oid[GIT_OID_HEXSZ];
167              
168 112           git_oid_fmt(hex_oid, oid);
169 112           git_buf_puts(buf, header);
170 112           git_buf_put(buf, hex_oid, GIT_OID_HEXSZ);
171 112           git_buf_putc(buf, '\n');
172 112           }
173              
174 35           int git_oid_fromraw(git_oid *out, const unsigned char *raw)
175             {
176 35           memcpy(out->id, raw, sizeof(out->id));
177 35           return 0;
178             }
179              
180 5286           int git_oid_cpy(git_oid *out, const git_oid *src)
181             {
182 5286           memcpy(out->id, src->id, sizeof(out->id));
183 5286           return 0;
184             }
185              
186 1312           int git_oid_cmp(const git_oid *a, const git_oid *b)
187             {
188 1312           return git_oid__cmp(a, b);
189             }
190              
191 6827           int git_oid_equal(const git_oid *a, const git_oid *b)
192             {
193 6827           return (git_oid__cmp(a, b) == 0);
194             }
195              
196 0           int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len)
197             {
198 0           const unsigned char *a = oid_a->id;
199 0           const unsigned char *b = oid_b->id;
200              
201 0 0         if (len > GIT_OID_HEXSZ)
202 0           len = GIT_OID_HEXSZ;
203              
204 0 0         while (len > 1) {
205 0 0         if (*a != *b)
206 0           return 1;
207 0           a++;
208 0           b++;
209 0           len -= 2;
210             };
211              
212 0 0         if (len)
213 0 0         if ((*a ^ *b) & 0xf0)
214 0           return 1;
215              
216 0           return 0;
217             }
218              
219 0           int git_oid_strcmp(const git_oid *oid_a, const char *str)
220             {
221             const unsigned char *a;
222             unsigned char strval;
223             int hexval;
224              
225 0 0         for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) {
    0          
226 0 0         if ((hexval = git__fromhex(*str++)) < 0)
227 0           return -1;
228 0           strval = (unsigned char)(hexval << 4);
229 0 0         if (*str) {
230 0 0         if ((hexval = git__fromhex(*str++)) < 0)
231 0           return -1;
232 0           strval |= hexval;
233             }
234 0 0         if (*a != strval)
235 0           return (*a - strval);
236             }
237              
238 0           return 0;
239             }
240              
241 0           int git_oid_streq(const git_oid *oid_a, const char *str)
242             {
243 0 0         return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1;
244             }
245              
246 3228           int git_oid_is_zero(const git_oid *oid_a)
247             {
248 3228           const unsigned char *a = oid_a->id;
249             unsigned int i;
250 24860 100         for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a)
251 23779 100         if (*a != 0)
252 2147           return 0;
253 1081           return 1;
254             }
255              
256             #ifndef GIT_DEPRECATE_HARD
257 0           int git_oid_iszero(const git_oid *oid_a)
258             {
259 0           return git_oid_is_zero(oid_a);
260             }
261             #endif
262              
263             typedef short node_index;
264              
265             typedef union {
266             const char *tail;
267             node_index children[16];
268             } trie_node;
269              
270             struct git_oid_shorten {
271             trie_node *nodes;
272             size_t node_count, size;
273             int min_length, full;
274             };
275              
276 0           static int resize_trie(git_oid_shorten *self, size_t new_size)
277             {
278 0           self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node));
279 0 0         GIT_ERROR_CHECK_ALLOC(self->nodes);
280              
281 0 0         if (new_size > self->size) {
282 0           memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node));
283             }
284              
285 0           self->size = new_size;
286 0           return 0;
287             }
288              
289 0           static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid)
290             {
291             trie_node *node, *leaf;
292             node_index idx_leaf;
293              
294 0 0         if (os->node_count >= os->size) {
295 0 0         if (resize_trie(os, os->size * 2) < 0)
296 0           return NULL;
297             }
298              
299 0           idx_leaf = (node_index)os->node_count++;
300              
301 0 0         if (os->node_count == SHRT_MAX) {
302 0           os->full = 1;
303 0           return NULL;
304             }
305              
306 0           node = &os->nodes[idx];
307 0           node->children[push_at] = -idx_leaf;
308              
309 0           leaf = &os->nodes[idx_leaf];
310 0           leaf->tail = oid;
311              
312 0           return node;
313             }
314              
315 0           git_oid_shorten *git_oid_shorten_new(size_t min_length)
316             {
317             git_oid_shorten *os;
318              
319 0 0         assert((size_t)((int)min_length) == min_length);
320              
321 0           os = git__calloc(1, sizeof(git_oid_shorten));
322 0 0         if (os == NULL)
323 0           return NULL;
324              
325 0 0         if (resize_trie(os, 16) < 0) {
326 0           git__free(os);
327 0           return NULL;
328             }
329              
330 0           os->node_count = 1;
331 0           os->min_length = (int)min_length;
332              
333 0           return os;
334             }
335              
336 0           void git_oid_shorten_free(git_oid_shorten *os)
337             {
338 0 0         if (os == NULL)
339 0           return;
340              
341 0           git__free(os->nodes);
342 0           git__free(os);
343             }
344              
345              
346             /*
347             * What wizardry is this?
348             *
349             * This is just a memory-optimized trie: basically a very fancy
350             * 16-ary tree, which is used to store the prefixes of the OID
351             * strings.
352             *
353             * Read more: http://en.wikipedia.org/wiki/Trie
354             *
355             * Magic that happens in this method:
356             *
357             * - Each node in the trie is an union, so it can work both as
358             * a normal node, or as a leaf.
359             *
360             * - Each normal node points to 16 children (one for each possible
361             * character in the oid). This is *not* stored in an array of
362             * pointers, because in a 64-bit arch this would be sucking
363             * 16*sizeof(void*) = 128 bytes of memory per node, which is
364             * insane. What we do is store Node Indexes, and use these indexes
365             * to look up each node in the om->index array. These indexes are
366             * signed shorts, so this limits the amount of unique OIDs that
367             * fit in the structure to about 20000 (assuming a more or less uniform
368             * distribution).
369             *
370             * - All the nodes in om->index array are stored contiguously in
371             * memory, and each of them is 32 bytes, so we fit 2x nodes per
372             * cache line. Convenient for speed.
373             *
374             * - To differentiate the leafs from the normal nodes, we store all
375             * the indexes towards a leaf as a negative index (indexes to normal
376             * nodes are positives). When we find that one of the children for
377             * a node has a negative value, that means it's going to be a leaf.
378             * This reduces the amount of indexes we have by two, but also reduces
379             * the size of each node by 1-4 bytes (the amount we would need to
380             * add a `is_leaf` field): this is good because it allows the nodes
381             * to fit cleanly in cache lines.
382             *
383             * - Once we reach an empty children, instead of continuing to insert
384             * new nodes for each remaining character of the OID, we store a pointer
385             * to the tail in the leaf; if the leaf is reached again, we turn it
386             * into a normal node and use the tail to create a new leaf.
387             *
388             * This is a pretty good balance between performance and memory usage.
389             */
390 0           int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid)
391             {
392             int i;
393             bool is_leaf;
394             node_index idx;
395              
396 0 0         if (os->full) {
397 0           git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full");
398 0           return -1;
399             }
400              
401 0 0         if (text_oid == NULL)
402 0           return os->min_length;
403              
404 0           idx = 0;
405 0           is_leaf = false;
406              
407 0 0         for (i = 0; i < GIT_OID_HEXSZ; ++i) {
408 0           int c = git__fromhex(text_oid[i]);
409             trie_node *node;
410              
411 0 0         if (c == -1) {
412 0           git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - invalid hex value");
413 0           return -1;
414             }
415              
416 0           node = &os->nodes[idx];
417              
418 0 0         if (is_leaf) {
419             const char *tail;
420              
421 0           tail = node->tail;
422 0           node->tail = NULL;
423              
424 0           node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]);
425 0 0         if (node == NULL) {
426 0 0         if (os->full)
427 0           git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full");
428 0           return -1;
429             }
430             }
431              
432 0 0         if (node->children[c] == 0) {
433 0 0         if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) {
434 0 0         if (os->full)
435 0           git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full");
436 0           return -1;
437             }
438 0           break;
439             }
440              
441 0           idx = node->children[c];
442 0           is_leaf = false;
443              
444 0 0         if (idx < 0) {
445 0           node->children[c] = idx = -idx;
446 0           is_leaf = true;
447             }
448             }
449              
450 0 0         if (++i > os->min_length)
451 0           os->min_length = i;
452              
453 0           return os->min_length;
454             }
455