]>
Commit | Line | Data |
---|---|---|
5d23e133 JH |
1 | #include "cache.h" |
2 | #include "diff.h" | |
3 | #include "commit.h" | |
4 | #include "patch-ids.h" | |
5 | ||
6 | static int commit_patch_id(struct commit *commit, struct diff_options *options, | |
7 | unsigned char *sha1) | |
8 | { | |
9 | if (commit->parents) | |
10 | diff_tree_sha1(commit->parents->item->object.sha1, | |
11 | commit->object.sha1, "", options); | |
12 | else | |
13 | diff_root_tree_sha1(commit->object.sha1, "", options); | |
14 | diffcore_std(options); | |
15 | return diff_flush_patch_id(options, sha1); | |
16 | } | |
17 | ||
18 | static uint32_t take2(const unsigned char *id) | |
19 | { | |
20 | return ((id[0] << 8) | id[1]); | |
21 | } | |
22 | ||
23 | /* | |
24 | * Conventional binary search loop looks like this: | |
25 | * | |
26 | * do { | |
27 | * int mi = (lo + hi) / 2; | |
28 | * int cmp = "entry pointed at by mi" minus "target"; | |
29 | * if (!cmp) | |
30 | * return (mi is the wanted one) | |
31 | * if (cmp > 0) | |
32 | * hi = mi; "mi is larger than target" | |
33 | * else | |
34 | * lo = mi+1; "mi is smaller than target" | |
35 | * } while (lo < hi); | |
36 | * | |
37 | * The invariants are: | |
38 | * | |
39 | * - When entering the loop, lo points at a slot that is never | |
40 | * above the target (it could be at the target), hi points at a | |
41 | * slot that is guaranteed to be above the target (it can never | |
42 | * be at the target). | |
43 | * | |
44 | * - We find a point 'mi' between lo and hi (mi could be the same | |
45 | * as lo, but never can be the same as hi), and check if it hits | |
46 | * the target. There are three cases: | |
47 | * | |
48 | * - if it is a hit, we are happy. | |
49 | * | |
50 | * - if it is strictly higher than the target, we update hi with | |
51 | * it. | |
52 | * | |
53 | * - if it is strictly lower than the target, we update lo to be | |
54 | * one slot after it, because we allow lo to be at the target. | |
55 | * | |
56 | * When choosing 'mi', we do not have to take the "middle" but | |
57 | * anywhere in between lo and hi, as long as lo <= mi < hi is | |
58 | * satisfied. When we somehow know that the distance between the | |
59 | * target and lo is much shorter than the target and hi, we could | |
60 | * pick mi that is much closer to lo than the midway. | |
61 | */ | |
62 | static int patch_pos(struct patch_id **table, int nr, const unsigned char *id) | |
63 | { | |
64 | int hi = nr; | |
65 | int lo = 0; | |
66 | int mi = 0; | |
67 | ||
68 | if (!nr) | |
69 | return -1; | |
70 | ||
71 | if (nr != 1) { | |
72 | unsigned lov, hiv, miv, ofs; | |
73 | ||
74 | for (ofs = 0; ofs < 18; ofs += 2) { | |
75 | lov = take2(table[0]->patch_id + ofs); | |
76 | hiv = take2(table[nr-1]->patch_id + ofs); | |
77 | miv = take2(id + ofs); | |
78 | if (miv < lov) | |
79 | return -1; | |
80 | if (hiv < miv) | |
81 | return -1 - nr; | |
82 | if (lov != hiv) { | |
83 | /* | |
84 | * At this point miv could be equal | |
85 | * to hiv (but id could still be higher); | |
86 | * the invariant of (mi < hi) should be | |
87 | * kept. | |
88 | */ | |
89 | mi = (nr-1) * (miv - lov) / (hiv - lov); | |
90 | if (lo <= mi && mi < hi) | |
91 | break; | |
92 | die("oops"); | |
93 | } | |
94 | } | |
95 | if (18 <= ofs) | |
96 | die("cannot happen -- lo and hi are identical"); | |
97 | } | |
98 | ||
99 | do { | |
100 | int cmp; | |
101 | cmp = hashcmp(table[mi]->patch_id, id); | |
102 | if (!cmp) | |
103 | return mi; | |
104 | if (cmp > 0) | |
105 | hi = mi; | |
106 | else | |
107 | lo = mi + 1; | |
108 | mi = (hi + lo) / 2; | |
109 | } while (lo < hi); | |
110 | return -lo-1; | |
111 | } | |
112 | ||
113 | #define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */ | |
114 | struct patch_id_bucket { | |
115 | struct patch_id_bucket *next; | |
116 | int nr; | |
117 | struct patch_id bucket[BUCKET_SIZE]; | |
118 | }; | |
119 | ||
120 | int init_patch_ids(struct patch_ids *ids) | |
121 | { | |
122 | memset(ids, 0, sizeof(*ids)); | |
123 | diff_setup(&ids->diffopts); | |
8f67f8ae | 124 | DIFF_OPT_SET(&ids->diffopts, RECURSIVE); |
5d23e133 JH |
125 | if (diff_setup_done(&ids->diffopts) < 0) |
126 | return error("diff_setup_done failed"); | |
127 | return 0; | |
128 | } | |
129 | ||
130 | int free_patch_ids(struct patch_ids *ids) | |
131 | { | |
132 | struct patch_id_bucket *next, *patches; | |
133 | ||
134 | free(ids->table); | |
135 | for (patches = ids->patches; patches; patches = next) { | |
136 | next = patches->next; | |
137 | free(patches); | |
138 | } | |
139 | return 0; | |
140 | } | |
141 | ||
142 | static struct patch_id *add_commit(struct commit *commit, | |
143 | struct patch_ids *ids, | |
144 | int no_add) | |
145 | { | |
146 | struct patch_id_bucket *bucket; | |
147 | struct patch_id *ent; | |
148 | unsigned char sha1[20]; | |
149 | int pos; | |
150 | ||
151 | if (commit_patch_id(commit, &ids->diffopts, sha1)) | |
152 | return NULL; | |
153 | pos = patch_pos(ids->table, ids->nr, sha1); | |
154 | if (0 <= pos) | |
155 | return ids->table[pos]; | |
156 | if (no_add) | |
157 | return NULL; | |
158 | ||
159 | pos = -1 - pos; | |
160 | ||
161 | bucket = ids->patches; | |
162 | if (!bucket || (BUCKET_SIZE <= bucket->nr)) { | |
163 | bucket = xcalloc(1, sizeof(*bucket)); | |
164 | bucket->next = ids->patches; | |
165 | ids->patches = bucket; | |
166 | } | |
167 | ent = &bucket->bucket[bucket->nr++]; | |
168 | hashcpy(ent->patch_id, sha1); | |
169 | ||
170 | if (ids->alloc <= ids->nr) { | |
171 | ids->alloc = alloc_nr(ids->nr); | |
172 | ids->table = xrealloc(ids->table, sizeof(ent) * ids->alloc); | |
173 | } | |
174 | if (pos < ids->nr) | |
175 | memmove(ids->table + pos + 1, ids->table + pos, | |
176 | sizeof(ent) * (ids->nr - pos)); | |
177 | ids->nr++; | |
178 | ids->table[pos] = ent; | |
179 | return ids->table[pos]; | |
180 | } | |
181 | ||
182 | struct patch_id *has_commit_patch_id(struct commit *commit, | |
183 | struct patch_ids *ids) | |
184 | { | |
185 | return add_commit(commit, ids, 1); | |
186 | } | |
187 | ||
188 | struct patch_id *add_commit_patch_id(struct commit *commit, | |
189 | struct patch_ids *ids) | |
190 | { | |
191 | return add_commit(commit, ids, 0); | |
192 | } |