bisect--helper: implement "git bisect--helper"
[git/spearce.git] / bisect.c
blob94ec011786cdff03fd143926aea7835dc2839a30
1 #include "cache.h"
2 #include "commit.h"
3 #include "diff.h"
4 #include "revision.h"
5 #include "refs.h"
6 #include "list-objects.h"
7 #include "sha1-lookup.h"
8 #include "bisect.h"
10 static unsigned char (*skipped_sha1)[20];
11 static int skipped_sha1_nr;
12 static int skipped_sha1_alloc;
14 static const char **rev_argv;
15 static int rev_argv_nr;
16 static int rev_argv_alloc;
18 /* bits #0-15 in revision.h */
20 #define COUNTED (1u<<16)
23 * This is a truly stupid algorithm, but it's only
24 * used for bisection, and we just don't care enough.
26 * We care just barely enough to avoid recursing for
27 * non-merge entries.
29 static int count_distance(struct commit_list *entry)
31 int nr = 0;
33 while (entry) {
34 struct commit *commit = entry->item;
35 struct commit_list *p;
37 if (commit->object.flags & (UNINTERESTING | COUNTED))
38 break;
39 if (!(commit->object.flags & TREESAME))
40 nr++;
41 commit->object.flags |= COUNTED;
42 p = commit->parents;
43 entry = p;
44 if (p) {
45 p = p->next;
46 while (p) {
47 nr += count_distance(p);
48 p = p->next;
53 return nr;
56 static void clear_distance(struct commit_list *list)
58 while (list) {
59 struct commit *commit = list->item;
60 commit->object.flags &= ~COUNTED;
61 list = list->next;
65 #define DEBUG_BISECT 0
67 static inline int weight(struct commit_list *elem)
69 return *((int*)(elem->item->util));
72 static inline void weight_set(struct commit_list *elem, int weight)
74 *((int*)(elem->item->util)) = weight;
77 static int count_interesting_parents(struct commit *commit)
79 struct commit_list *p;
80 int count;
82 for (count = 0, p = commit->parents; p; p = p->next) {
83 if (p->item->object.flags & UNINTERESTING)
84 continue;
85 count++;
87 return count;
90 static inline int halfway(struct commit_list *p, int nr)
93 * Don't short-cut something we are not going to return!
95 if (p->item->object.flags & TREESAME)
96 return 0;
97 if (DEBUG_BISECT)
98 return 0;
100 * 2 and 3 are halfway of 5.
101 * 3 is halfway of 6 but 2 and 4 are not.
103 switch (2 * weight(p) - nr) {
104 case -1: case 0: case 1:
105 return 1;
106 default:
107 return 0;
111 #if !DEBUG_BISECT
112 #define show_list(a,b,c,d) do { ; } while (0)
113 #else
114 static void show_list(const char *debug, int counted, int nr,
115 struct commit_list *list)
117 struct commit_list *p;
119 fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
121 for (p = list; p; p = p->next) {
122 struct commit_list *pp;
123 struct commit *commit = p->item;
124 unsigned flags = commit->object.flags;
125 enum object_type type;
126 unsigned long size;
127 char *buf = read_sha1_file(commit->object.sha1, &type, &size);
128 char *ep, *sp;
130 fprintf(stderr, "%c%c%c ",
131 (flags & TREESAME) ? ' ' : 'T',
132 (flags & UNINTERESTING) ? 'U' : ' ',
133 (flags & COUNTED) ? 'C' : ' ');
134 if (commit->util)
135 fprintf(stderr, "%3d", weight(p));
136 else
137 fprintf(stderr, "---");
138 fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
139 for (pp = commit->parents; pp; pp = pp->next)
140 fprintf(stderr, " %.*s", 8,
141 sha1_to_hex(pp->item->object.sha1));
143 sp = strstr(buf, "\n\n");
144 if (sp) {
145 sp += 2;
146 for (ep = sp; *ep && *ep != '\n'; ep++)
148 fprintf(stderr, " %.*s", (int)(ep - sp), sp);
150 fprintf(stderr, "\n");
153 #endif /* DEBUG_BISECT */
155 static struct commit_list *best_bisection(struct commit_list *list, int nr)
157 struct commit_list *p, *best;
158 int best_distance = -1;
160 best = list;
161 for (p = list; p; p = p->next) {
162 int distance;
163 unsigned flags = p->item->object.flags;
165 if (flags & TREESAME)
166 continue;
167 distance = weight(p);
168 if (nr - distance < distance)
169 distance = nr - distance;
170 if (distance > best_distance) {
171 best = p;
172 best_distance = distance;
176 return best;
179 struct commit_dist {
180 struct commit *commit;
181 int distance;
184 static int compare_commit_dist(const void *a_, const void *b_)
186 struct commit_dist *a, *b;
188 a = (struct commit_dist *)a_;
189 b = (struct commit_dist *)b_;
190 if (a->distance != b->distance)
191 return b->distance - a->distance; /* desc sort */
192 return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
195 static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
197 struct commit_list *p;
198 struct commit_dist *array = xcalloc(nr, sizeof(*array));
199 int cnt, i;
201 for (p = list, cnt = 0; p; p = p->next) {
202 int distance;
203 unsigned flags = p->item->object.flags;
205 if (flags & TREESAME)
206 continue;
207 distance = weight(p);
208 if (nr - distance < distance)
209 distance = nr - distance;
210 array[cnt].commit = p->item;
211 array[cnt].distance = distance;
212 cnt++;
214 qsort(array, cnt, sizeof(*array), compare_commit_dist);
215 for (p = list, i = 0; i < cnt; i++) {
216 struct name_decoration *r = xmalloc(sizeof(*r) + 100);
217 struct object *obj = &(array[i].commit->object);
219 sprintf(r->name, "dist=%d", array[i].distance);
220 r->next = add_decoration(&name_decoration, obj, r);
221 p->item = array[i].commit;
222 p = p->next;
224 if (p)
225 p->next = NULL;
226 free(array);
227 return list;
231 * zero or positive weight is the number of interesting commits it can
232 * reach, including itself. Especially, weight = 0 means it does not
233 * reach any tree-changing commits (e.g. just above uninteresting one
234 * but traversal is with pathspec).
236 * weight = -1 means it has one parent and its distance is yet to
237 * be computed.
239 * weight = -2 means it has more than one parent and its distance is
240 * unknown. After running count_distance() first, they will get zero
241 * or positive distance.
243 static struct commit_list *do_find_bisection(struct commit_list *list,
244 int nr, int *weights,
245 int find_all)
247 int n, counted;
248 struct commit_list *p;
250 counted = 0;
252 for (n = 0, p = list; p; p = p->next) {
253 struct commit *commit = p->item;
254 unsigned flags = commit->object.flags;
256 p->item->util = &weights[n++];
257 switch (count_interesting_parents(commit)) {
258 case 0:
259 if (!(flags & TREESAME)) {
260 weight_set(p, 1);
261 counted++;
262 show_list("bisection 2 count one",
263 counted, nr, list);
266 * otherwise, it is known not to reach any
267 * tree-changing commit and gets weight 0.
269 break;
270 case 1:
271 weight_set(p, -1);
272 break;
273 default:
274 weight_set(p, -2);
275 break;
279 show_list("bisection 2 initialize", counted, nr, list);
282 * If you have only one parent in the resulting set
283 * then you can reach one commit more than that parent
284 * can reach. So we do not have to run the expensive
285 * count_distance() for single strand of pearls.
287 * However, if you have more than one parents, you cannot
288 * just add their distance and one for yourself, since
289 * they usually reach the same ancestor and you would
290 * end up counting them twice that way.
292 * So we will first count distance of merges the usual
293 * way, and then fill the blanks using cheaper algorithm.
295 for (p = list; p; p = p->next) {
296 if (p->item->object.flags & UNINTERESTING)
297 continue;
298 if (weight(p) != -2)
299 continue;
300 weight_set(p, count_distance(p));
301 clear_distance(list);
303 /* Does it happen to be at exactly half-way? */
304 if (!find_all && halfway(p, nr))
305 return p;
306 counted++;
309 show_list("bisection 2 count_distance", counted, nr, list);
311 while (counted < nr) {
312 for (p = list; p; p = p->next) {
313 struct commit_list *q;
314 unsigned flags = p->item->object.flags;
316 if (0 <= weight(p))
317 continue;
318 for (q = p->item->parents; q; q = q->next) {
319 if (q->item->object.flags & UNINTERESTING)
320 continue;
321 if (0 <= weight(q))
322 break;
324 if (!q)
325 continue;
328 * weight for p is unknown but q is known.
329 * add one for p itself if p is to be counted,
330 * otherwise inherit it from q directly.
332 if (!(flags & TREESAME)) {
333 weight_set(p, weight(q)+1);
334 counted++;
335 show_list("bisection 2 count one",
336 counted, nr, list);
338 else
339 weight_set(p, weight(q));
341 /* Does it happen to be at exactly half-way? */
342 if (!find_all && halfway(p, nr))
343 return p;
347 show_list("bisection 2 counted all", counted, nr, list);
349 if (!find_all)
350 return best_bisection(list, nr);
351 else
352 return best_bisection_sorted(list, nr);
355 struct commit_list *find_bisection(struct commit_list *list,
356 int *reaches, int *all,
357 int find_all)
359 int nr, on_list;
360 struct commit_list *p, *best, *next, *last;
361 int *weights;
363 show_list("bisection 2 entry", 0, 0, list);
366 * Count the number of total and tree-changing items on the
367 * list, while reversing the list.
369 for (nr = on_list = 0, last = NULL, p = list;
371 p = next) {
372 unsigned flags = p->item->object.flags;
374 next = p->next;
375 if (flags & UNINTERESTING)
376 continue;
377 p->next = last;
378 last = p;
379 if (!(flags & TREESAME))
380 nr++;
381 on_list++;
383 list = last;
384 show_list("bisection 2 sorted", 0, nr, list);
386 *all = nr;
387 weights = xcalloc(on_list, sizeof(*weights));
389 /* Do the real work of finding bisection commit. */
390 best = do_find_bisection(list, nr, weights, find_all);
391 if (best) {
392 if (!find_all)
393 best->next = NULL;
394 *reaches = weight(best);
396 free(weights);
397 return best;
400 static int register_ref(const char *refname, const unsigned char *sha1,
401 int flags, void *cb_data)
403 if (!strcmp(refname, "bad")) {
404 ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
405 rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1));
406 } else if (!prefixcmp(refname, "good-")) {
407 const char *hex = sha1_to_hex(sha1);
408 char *good = xmalloc(strlen(hex) + 2);
409 *good = '^';
410 memcpy(good + 1, hex, strlen(hex) + 1);
411 ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
412 rev_argv[rev_argv_nr++] = good;
413 } else if (!prefixcmp(refname, "skip-")) {
414 ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1,
415 skipped_sha1_alloc);
416 hashcpy(skipped_sha1[skipped_sha1_nr++], sha1);
419 return 0;
422 static int read_bisect_refs(void)
424 return for_each_ref_in("refs/bisect/", register_ref, NULL);
427 static int skipcmp(const void *a, const void *b)
429 return hashcmp(a, b);
432 static void prepare_skipped(void)
434 qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp);
437 static const unsigned char *skipped_sha1_access(size_t index, void *table)
439 unsigned char (*skipped)[20] = table;
440 return skipped[index];
443 static int lookup_skipped(unsigned char *sha1)
445 return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr,
446 skipped_sha1_access);
449 struct commit_list *filter_skipped(struct commit_list *list,
450 struct commit_list **tried,
451 int show_all)
453 struct commit_list *filtered = NULL, **f = &filtered;
455 *tried = NULL;
457 if (!skipped_sha1_nr)
458 return list;
460 prepare_skipped();
462 while (list) {
463 struct commit_list *next = list->next;
464 list->next = NULL;
465 if (0 <= lookup_skipped(list->item->object.sha1)) {
466 /* Move current to tried list */
467 *tried = list;
468 tried = &list->next;
469 } else {
470 if (!show_all)
471 return list;
472 /* Move current to filtered list */
473 *f = list;
474 f = &list->next;
476 list = next;
479 return filtered;
482 int bisect_next_vars(const char *prefix)
484 struct rev_info revs;
485 int reaches = 0, all = 0;
487 init_revisions(&revs, prefix);
488 revs.abbrev = 0;
489 revs.commit_format = CMIT_FMT_UNSPECIFIED;
491 /* argv[0] will be ignored by setup_revisions */
492 ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
493 rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup");
495 if (read_bisect_refs())
496 die("reading bisect refs failed");
498 ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
499 rev_argv[rev_argv_nr++] = xstrdup("--");
501 setup_revisions(rev_argv_nr, rev_argv, &revs, NULL);
503 revs.limited = 1;
505 if (prepare_revision_walk(&revs))
506 die("revision walk setup failed");
507 if (revs.tree_objects)
508 mark_edges_uninteresting(revs.commits, &revs, NULL);
510 revs.commits = find_bisection(revs.commits, &reaches, &all,
511 !!skipped_sha1_nr);
513 return show_bisect_vars(&revs, reaches, all, 0, 1);