]> git.proxmox.com Git - libgit2.git/blob - src/revwalk.c
1e3a5f2ff1062efa67f76c7872318269943699a1
[libgit2.git] / src / revwalk.c
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 "revwalk.h"
9
10 #include "commit.h"
11 #include "odb.h"
12 #include "pool.h"
13
14 #include "git2/revparse.h"
15 #include "merge.h"
16 #include "vector.h"
17
18 static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list);
19
20 git_commit_list_node *git_revwalk__commit_lookup(
21 git_revwalk *walk, const git_oid *oid)
22 {
23 git_commit_list_node *commit;
24 size_t pos;
25 int ret;
26
27 /* lookup and reserve space if not already present */
28 pos = git_oidmap_lookup_index(walk->commits, oid);
29 if (git_oidmap_valid_index(walk->commits, pos))
30 return git_oidmap_value_at(walk->commits, pos);
31
32 commit = git_commit_list_alloc_node(walk);
33 if (commit == NULL)
34 return NULL;
35
36 git_oid_cpy(&commit->oid, oid);
37
38 pos = git_oidmap_put(walk->commits, &commit->oid, &ret);
39 assert(ret != 0);
40 git_oidmap_set_value_at(walk->commits, pos, commit);
41
42 return commit;
43 }
44
45 static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob)
46 {
47 git_oid commit_id;
48 int error;
49 git_object *obj, *oobj;
50 git_commit_list_node *commit;
51 git_commit_list *list;
52
53 if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJECT_ANY)) < 0)
54 return error;
55
56 error = git_object_peel(&obj, oobj, GIT_OBJECT_COMMIT);
57 git_object_free(oobj);
58
59 if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) {
60 /* If this comes from e.g. push_glob("tags"), ignore this */
61 if (from_glob)
62 return 0;
63
64 git_error_set(GIT_ERROR_INVALID, "object is not a committish");
65 return -1;
66 }
67 if (error < 0)
68 return error;
69
70 git_oid_cpy(&commit_id, git_object_id(obj));
71 git_object_free(obj);
72
73 commit = git_revwalk__commit_lookup(walk, &commit_id);
74 if (commit == NULL)
75 return -1; /* error already reported by failed lookup */
76
77 /* A previous hide already told us we don't want this commit */
78 if (commit->uninteresting)
79 return 0;
80
81 if (uninteresting) {
82 walk->limited = 1;
83 walk->did_hide = 1;
84 } else {
85 walk->did_push = 1;
86 }
87
88 commit->uninteresting = uninteresting;
89 list = walk->user_input;
90 if (git_commit_list_insert(commit, &list) == NULL) {
91 git_error_set_oom();
92 return -1;
93 }
94
95 walk->user_input = list;
96
97 return 0;
98 }
99
100 int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
101 {
102 assert(walk && oid);
103 return push_commit(walk, oid, 0, false);
104 }
105
106
107 int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
108 {
109 assert(walk && oid);
110 return push_commit(walk, oid, 1, false);
111 }
112
113 static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob)
114 {
115 git_oid oid;
116
117 if (git_reference_name_to_id(&oid, walk->repo, refname) < 0)
118 return -1;
119
120 return push_commit(walk, &oid, hide, from_glob);
121 }
122
123 static int push_glob(git_revwalk *walk, const char *glob, int hide)
124 {
125 int error = 0;
126 git_buf buf = GIT_BUF_INIT;
127 git_reference *ref;
128 git_reference_iterator *iter;
129 size_t wildcard;
130
131 assert(walk && glob);
132
133 /* refs/ is implied if not given in the glob */
134 if (git__prefixcmp(glob, GIT_REFS_DIR) != 0)
135 git_buf_joinpath(&buf, GIT_REFS_DIR, glob);
136 else
137 git_buf_puts(&buf, glob);
138 GIT_ERROR_CHECK_ALLOC_BUF(&buf);
139
140 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
141 wildcard = strcspn(glob, "?*[");
142 if (!glob[wildcard])
143 git_buf_put(&buf, "/*", 2);
144
145 if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0)
146 goto out;
147
148 while ((error = git_reference_next(&ref, iter)) == 0) {
149 error = push_ref(walk, git_reference_name(ref), hide, true);
150 git_reference_free(ref);
151 if (error < 0)
152 break;
153 }
154 git_reference_iterator_free(iter);
155
156 if (error == GIT_ITEROVER)
157 error = 0;
158 out:
159 git_buf_dispose(&buf);
160 return error;
161 }
162
163 int git_revwalk_push_glob(git_revwalk *walk, const char *glob)
164 {
165 assert(walk && glob);
166 return push_glob(walk, glob, 0);
167 }
168
169 int git_revwalk_hide_glob(git_revwalk *walk, const char *glob)
170 {
171 assert(walk && glob);
172 return push_glob(walk, glob, 1);
173 }
174
175 int git_revwalk_push_head(git_revwalk *walk)
176 {
177 assert(walk);
178 return push_ref(walk, GIT_HEAD_FILE, 0, false);
179 }
180
181 int git_revwalk_hide_head(git_revwalk *walk)
182 {
183 assert(walk);
184 return push_ref(walk, GIT_HEAD_FILE, 1, false);
185 }
186
187 int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
188 {
189 assert(walk && refname);
190 return push_ref(walk, refname, 0, false);
191 }
192
193 int git_revwalk_push_range(git_revwalk *walk, const char *range)
194 {
195 git_revspec revspec;
196 int error = 0;
197
198 if ((error = git_revparse(&revspec, walk->repo, range)))
199 return error;
200
201 if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
202 /* TODO: support "<commit>...<commit>" */
203 git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk");
204 return GIT_EINVALIDSPEC;
205 }
206
207 if ((error = push_commit(walk, git_object_id(revspec.from), 1, false)))
208 goto out;
209
210 error = push_commit(walk, git_object_id(revspec.to), 0, false);
211
212 out:
213 git_object_free(revspec.from);
214 git_object_free(revspec.to);
215 return error;
216 }
217
218 int git_revwalk_hide_ref(git_revwalk *walk, const char *refname)
219 {
220 assert(walk && refname);
221 return push_ref(walk, refname, 1, false);
222 }
223
224 static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit)
225 {
226 return git_pqueue_insert(&walk->iterator_time, commit);
227 }
228
229 static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit)
230 {
231 return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
232 }
233
234 static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk)
235 {
236 git_commit_list_node *next;
237
238 while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
239 /* Some commits might become uninteresting after being added to the list */
240 if (!next->uninteresting) {
241 *object_out = next;
242 return 0;
243 }
244 }
245
246 git_error_clear();
247 return GIT_ITEROVER;
248 }
249
250 static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk)
251 {
252 int error;
253 git_commit_list_node *next;
254
255 while (!(error = get_revision(&next, walk, &walk->iterator_rand))) {
256 /* Some commits might become uninteresting after being added to the list */
257 if (!next->uninteresting) {
258 *object_out = next;
259 return 0;
260 }
261 }
262
263 return error;
264 }
265
266 static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk)
267 {
268 int error;
269 git_commit_list_node *next;
270
271 while (!(error = get_revision(&next, walk, &walk->iterator_topo))) {
272 /* Some commits might become uninteresting after being added to the list */
273 if (!next->uninteresting) {
274 *object_out = next;
275 return 0;
276 }
277 }
278
279 return error;
280 }
281
282 static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk)
283 {
284 *object_out = git_commit_list_pop(&walk->iterator_reverse);
285 return *object_out ? 0 : GIT_ITEROVER;
286 }
287
288 static void mark_parents_uninteresting(git_commit_list_node *commit)
289 {
290 unsigned short i;
291 git_commit_list *parents = NULL;
292
293 for (i = 0; i < commit->out_degree; i++)
294 git_commit_list_insert(commit->parents[i], &parents);
295
296
297 while (parents) {
298 commit = git_commit_list_pop(&parents);
299
300 while (commit) {
301 if (commit->uninteresting)
302 break;
303
304 commit->uninteresting = 1;
305 /*
306 * If we've reached this commit some other way
307 * already, we need to mark its parents uninteresting
308 * as well.
309 */
310 if (!commit->parents)
311 break;
312
313 for (i = 0; i < commit->out_degree; i++)
314 git_commit_list_insert(commit->parents[i], &parents);
315 commit = commit->parents[0];
316 }
317 }
318 }
319
320 static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list)
321 {
322 unsigned short i;
323 int error;
324
325 if (commit->added)
326 return 0;
327
328 commit->added = 1;
329
330 /*
331 * Go full on in the uninteresting case as we want to include
332 * as many of these as we can.
333 *
334 * Usually we haven't parsed the parent of a parent, but if we
335 * have it we reached it via other means so we want to mark
336 * its parents recursively too.
337 */
338 if (commit->uninteresting) {
339 for (i = 0; i < commit->out_degree; i++) {
340 git_commit_list_node *p = commit->parents[i];
341 p->uninteresting = 1;
342
343 /* git does it gently here, but we don't like missing objects */
344 if ((error = git_commit_list_parse(walk, p)) < 0)
345 return error;
346
347 if (p->parents)
348 mark_parents_uninteresting(p);
349
350 p->seen = 1;
351 git_commit_list_insert_by_date(p, list);
352 }
353
354 return 0;
355 }
356
357 /*
358 * Now on to what we do if the commit is indeed
359 * interesting. Here we do want things like first-parent take
360 * effect as this is what we'll be showing.
361 */
362 for (i = 0; i < commit->out_degree; i++) {
363 git_commit_list_node *p = commit->parents[i];
364
365 if ((error = git_commit_list_parse(walk, p)) < 0)
366 return error;
367
368 if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload))
369 continue;
370
371 if (!p->seen) {
372 p->seen = 1;
373 git_commit_list_insert_by_date(p, list);
374 }
375
376 if (walk->first_parent)
377 break;
378 }
379 return 0;
380 }
381
382 /* How many unintersting commits we want to look at after we run out of interesting ones */
383 #define SLOP 5
384
385 static int still_interesting(git_commit_list *list, int64_t time, int slop)
386 {
387 /* The empty list is pretty boring */
388 if (!list)
389 return 0;
390
391 /*
392 * If the destination list has commits with an earlier date than our
393 * source, we want to reset the slop counter as we're not done.
394 */
395 if (time <= list->item->time)
396 return SLOP;
397
398 for (; list; list = list->next) {
399 /*
400 * If the destination list still contains interesting commits we
401 * want to continue looking.
402 */
403 if (!list->item->uninteresting || list->item->time > time)
404 return SLOP;
405 }
406
407 /* Everything's uninteresting, reduce the count */
408 return slop - 1;
409 }
410
411 static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits)
412 {
413 int error, slop = SLOP;
414 int64_t time = INT64_MAX;
415 git_commit_list *list = commits;
416 git_commit_list *newlist = NULL;
417 git_commit_list **p = &newlist;
418
419 while (list) {
420 git_commit_list_node *commit = git_commit_list_pop(&list);
421
422 if ((error = add_parents_to_list(walk, commit, &list)) < 0)
423 return error;
424
425 if (commit->uninteresting) {
426 mark_parents_uninteresting(commit);
427
428 slop = still_interesting(list, time, slop);
429 if (slop)
430 continue;
431
432 break;
433 }
434
435 if (walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload))
436 continue;
437
438 time = commit->time;
439 p = &git_commit_list_insert(commit, p)->next;
440 }
441
442 git_commit_list_free(&list);
443 *out = newlist;
444 return 0;
445 }
446
447 static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list)
448 {
449 int error;
450 git_commit_list_node *commit;
451
452 commit = git_commit_list_pop(list);
453 if (!commit) {
454 git_error_clear();
455 return GIT_ITEROVER;
456 }
457
458 /*
459 * If we did not run limit_list and we must add parents to the
460 * list ourselves.
461 */
462 if (!walk->limited) {
463 if ((error = add_parents_to_list(walk, commit, list)) < 0)
464 return error;
465 }
466
467 *out = commit;
468 return 0;
469 }
470
471 static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list)
472 {
473 git_commit_list *ll = NULL, *newlist, **pptr;
474 git_commit_list_node *next;
475 git_pqueue queue;
476 git_vector_cmp queue_cmp = NULL;
477 unsigned short i;
478 int error;
479
480 if (walk->sorting & GIT_SORT_TIME)
481 queue_cmp = git_commit_list_time_cmp;
482
483 if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp)))
484 return error;
485
486 /*
487 * Start by resetting the in-degree to 1 for the commits in
488 * our list. We want to go through this list again, so we
489 * store it in the commit list as we extract it from the lower
490 * machinery.
491 */
492 for (ll = list; ll; ll = ll->next) {
493 ll->item->in_degree = 1;
494 }
495
496 /*
497 * Count up how many children each commit has. We limit
498 * ourselves to those commits in the original list (in-degree
499 * of 1) avoiding setting it for any parent that was hidden.
500 */
501 for(ll = list; ll; ll = ll->next) {
502 for (i = 0; i < ll->item->out_degree; ++i) {
503 git_commit_list_node *parent = ll->item->parents[i];
504 if (parent->in_degree)
505 parent->in_degree++;
506 }
507 }
508
509 /*
510 * Now we find the tips i.e. those not reachable from any other node
511 * i.e. those which still have an in-degree of 1.
512 */
513 for(ll = list; ll; ll = ll->next) {
514 if (ll->item->in_degree == 1) {
515 if ((error = git_pqueue_insert(&queue, ll->item)))
516 goto cleanup;
517 }
518 }
519
520 /*
521 * We need to output the tips in the order that they came out of the
522 * traversal, so if we're not doing time-sorting, we need to reverse the
523 * pqueue in order to get them to come out as we inserted them.
524 */
525 if ((walk->sorting & GIT_SORT_TIME) == 0)
526 git_pqueue_reverse(&queue);
527
528
529 pptr = &newlist;
530 newlist = NULL;
531 while ((next = git_pqueue_pop(&queue)) != NULL) {
532 for (i = 0; i < next->out_degree; ++i) {
533 git_commit_list_node *parent = next->parents[i];
534 if (parent->in_degree == 0)
535 continue;
536
537 if (--parent->in_degree == 1) {
538 if ((error = git_pqueue_insert(&queue, parent)))
539 goto cleanup;
540 }
541 }
542
543 /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
544 next->in_degree = 0;
545
546 pptr = &git_commit_list_insert(next, pptr)->next;
547 }
548
549 *out = newlist;
550 error = 0;
551
552 cleanup:
553 git_pqueue_free(&queue);
554 return error;
555 }
556
557 static int prepare_walk(git_revwalk *walk)
558 {
559 int error = 0;
560 git_commit_list *list, *commits = NULL;
561 git_commit_list_node *next;
562
563 /* If there were no pushes, we know that the walk is already over */
564 if (!walk->did_push) {
565 git_error_clear();
566 return GIT_ITEROVER;
567 }
568
569 for (list = walk->user_input; list; list = list->next) {
570 git_commit_list_node *commit = list->item;
571 if ((error = git_commit_list_parse(walk, commit)) < 0)
572 return error;
573
574 if (commit->uninteresting)
575 mark_parents_uninteresting(commit);
576
577 if (!commit->seen) {
578 commit->seen = 1;
579 git_commit_list_insert(commit, &commits);
580 }
581 }
582
583 if (walk->limited && (error = limit_list(&commits, walk, commits)) < 0)
584 return error;
585
586 if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
587 error = sort_in_topological_order(&walk->iterator_topo, walk, commits);
588 git_commit_list_free(&commits);
589
590 if (error < 0)
591 return error;
592
593 walk->get_next = &revwalk_next_toposort;
594 } else if (walk->sorting & GIT_SORT_TIME) {
595 for (list = commits; list && !error; list = list->next)
596 error = walk->enqueue(walk, list->item);
597
598 git_commit_list_free(&commits);
599
600 if (error < 0)
601 return error;
602 } else {
603 walk->iterator_rand = commits;
604 walk->get_next = revwalk_next_unsorted;
605 }
606
607 if (walk->sorting & GIT_SORT_REVERSE) {
608
609 while ((error = walk->get_next(&next, walk)) == 0)
610 if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL)
611 return -1;
612
613 if (error != GIT_ITEROVER)
614 return error;
615
616 walk->get_next = &revwalk_next_reverse;
617 }
618
619 walk->walking = 1;
620 return 0;
621 }
622
623
624 int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
625 {
626 git_revwalk *walk = git__calloc(1, sizeof(git_revwalk));
627 GIT_ERROR_CHECK_ALLOC(walk);
628
629 walk->commits = git_oidmap_alloc();
630 GIT_ERROR_CHECK_ALLOC(walk->commits);
631
632 if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0)
633 return -1;
634
635 git_pool_init(&walk->commit_pool, COMMIT_ALLOC);
636 walk->get_next = &revwalk_next_unsorted;
637 walk->enqueue = &revwalk_enqueue_unsorted;
638
639 walk->repo = repo;
640
641 if (git_repository_odb(&walk->odb, repo) < 0) {
642 git_revwalk_free(walk);
643 return -1;
644 }
645
646 *revwalk_out = walk;
647 return 0;
648 }
649
650 void git_revwalk_free(git_revwalk *walk)
651 {
652 if (walk == NULL)
653 return;
654
655 git_revwalk_reset(walk);
656 git_odb_free(walk->odb);
657
658 git_oidmap_free(walk->commits);
659 git_pool_clear(&walk->commit_pool);
660 git_pqueue_free(&walk->iterator_time);
661 git__free(walk);
662 }
663
664 git_repository *git_revwalk_repository(git_revwalk *walk)
665 {
666 assert(walk);
667 return walk->repo;
668 }
669
670 void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
671 {
672 assert(walk);
673
674 if (walk->walking)
675 git_revwalk_reset(walk);
676
677 walk->sorting = sort_mode;
678
679 if (walk->sorting & GIT_SORT_TIME) {
680 walk->get_next = &revwalk_next_timesort;
681 walk->enqueue = &revwalk_enqueue_timesort;
682 } else {
683 walk->get_next = &revwalk_next_unsorted;
684 walk->enqueue = &revwalk_enqueue_unsorted;
685 }
686
687 if (walk->sorting != GIT_SORT_NONE)
688 walk->limited = 1;
689 }
690
691 void git_revwalk_simplify_first_parent(git_revwalk *walk)
692 {
693 walk->first_parent = 1;
694 }
695
696 int git_revwalk_next(git_oid *oid, git_revwalk *walk)
697 {
698 int error;
699 git_commit_list_node *next;
700
701 assert(walk && oid);
702
703 if (!walk->walking) {
704 if ((error = prepare_walk(walk)) < 0)
705 return error;
706 }
707
708 error = walk->get_next(&next, walk);
709
710 if (error == GIT_ITEROVER) {
711 git_revwalk_reset(walk);
712 git_error_clear();
713 return GIT_ITEROVER;
714 }
715
716 if (!error)
717 git_oid_cpy(oid, &next->oid);
718
719 return error;
720 }
721
722 void git_revwalk_reset(git_revwalk *walk)
723 {
724 git_commit_list_node *commit;
725
726 assert(walk);
727
728 git_oidmap_foreach_value(walk->commits, commit, {
729 commit->seen = 0;
730 commit->in_degree = 0;
731 commit->topo_delay = 0;
732 commit->uninteresting = 0;
733 commit->added = 0;
734 commit->flags = 0;
735 });
736
737 git_pqueue_clear(&walk->iterator_time);
738 git_commit_list_free(&walk->iterator_topo);
739 git_commit_list_free(&walk->iterator_rand);
740 git_commit_list_free(&walk->iterator_reverse);
741 git_commit_list_free(&walk->user_input);
742 walk->first_parent = 0;
743 walk->walking = 0;
744 walk->limited = 0;
745 walk->did_push = walk->did_hide = 0;
746 walk->sorting = GIT_SORT_NONE;
747 }
748
749 int git_revwalk_add_hide_cb(
750 git_revwalk *walk,
751 git_revwalk_hide_cb hide_cb,
752 void *payload)
753 {
754 assert(walk);
755
756 if (walk->walking)
757 git_revwalk_reset(walk);
758
759 walk->hide_cb = hide_cb;
760 walk->hide_cb_payload = payload;
761
762 if (hide_cb)
763 walk->limited = 1;
764
765 return 0;
766 }
767