2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
14 #include "git2/revparse.h"
18 static int get_revision(git_commit_list_node
**out
, git_revwalk
*walk
, git_commit_list
**list
);
20 git_commit_list_node
*git_revwalk__commit_lookup(
21 git_revwalk
*walk
, const git_oid
*oid
)
23 git_commit_list_node
*commit
;
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
);
32 commit
= git_commit_list_alloc_node(walk
);
36 git_oid_cpy(&commit
->oid
, oid
);
38 pos
= git_oidmap_put(walk
->commits
, &commit
->oid
, &ret
);
40 git_oidmap_set_value_at(walk
->commits
, pos
, commit
);
45 static int push_commit(git_revwalk
*walk
, const git_oid
*oid
, int uninteresting
, int from_glob
)
49 git_object
*obj
, *oobj
;
50 git_commit_list_node
*commit
;
51 git_commit_list
*list
;
53 if ((error
= git_object_lookup(&oobj
, walk
->repo
, oid
, GIT_OBJECT_ANY
)) < 0)
56 error
= git_object_peel(&obj
, oobj
, GIT_OBJECT_COMMIT
);
57 git_object_free(oobj
);
59 if (error
== GIT_ENOTFOUND
|| error
== GIT_EINVALIDSPEC
|| error
== GIT_EPEEL
) {
60 /* If this comes from e.g. push_glob("tags"), ignore this */
64 git_error_set(GIT_ERROR_INVALID
, "object is not a committish");
70 git_oid_cpy(&commit_id
, git_object_id(obj
));
73 commit
= git_revwalk__commit_lookup(walk
, &commit_id
);
75 return -1; /* error already reported by failed lookup */
77 /* A previous hide already told us we don't want this commit */
78 if (commit
->uninteresting
)
88 commit
->uninteresting
= uninteresting
;
89 list
= walk
->user_input
;
90 if (git_commit_list_insert(commit
, &list
) == NULL
) {
95 walk
->user_input
= list
;
100 int git_revwalk_push(git_revwalk
*walk
, const git_oid
*oid
)
103 return push_commit(walk
, oid
, 0, false);
107 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
110 return push_commit(walk
, oid
, 1, false);
113 static int push_ref(git_revwalk
*walk
, const char *refname
, int hide
, int from_glob
)
117 if (git_reference_name_to_id(&oid
, walk
->repo
, refname
) < 0)
120 return push_commit(walk
, &oid
, hide
, from_glob
);
123 static int push_glob(git_revwalk
*walk
, const char *glob
, int hide
)
126 git_buf buf
= GIT_BUF_INIT
;
128 git_reference_iterator
*iter
;
131 assert(walk
&& glob
);
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
);
137 git_buf_puts(&buf
, glob
);
138 GIT_ERROR_CHECK_ALLOC_BUF(&buf
);
140 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
141 wildcard
= strcspn(glob
, "?*[");
143 git_buf_put(&buf
, "/*", 2);
145 if ((error
= git_reference_iterator_glob_new(&iter
, walk
->repo
, buf
.ptr
)) < 0)
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
);
154 git_reference_iterator_free(iter
);
156 if (error
== GIT_ITEROVER
)
159 git_buf_dispose(&buf
);
163 int git_revwalk_push_glob(git_revwalk
*walk
, const char *glob
)
165 assert(walk
&& glob
);
166 return push_glob(walk
, glob
, 0);
169 int git_revwalk_hide_glob(git_revwalk
*walk
, const char *glob
)
171 assert(walk
&& glob
);
172 return push_glob(walk
, glob
, 1);
175 int git_revwalk_push_head(git_revwalk
*walk
)
178 return push_ref(walk
, GIT_HEAD_FILE
, 0, false);
181 int git_revwalk_hide_head(git_revwalk
*walk
)
184 return push_ref(walk
, GIT_HEAD_FILE
, 1, false);
187 int git_revwalk_push_ref(git_revwalk
*walk
, const char *refname
)
189 assert(walk
&& refname
);
190 return push_ref(walk
, refname
, 0, false);
193 int git_revwalk_push_range(git_revwalk
*walk
, const char *range
)
198 if ((error
= git_revparse(&revspec
, walk
->repo
, range
)))
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
;
207 if ((error
= push_commit(walk
, git_object_id(revspec
.from
), 1, false)))
210 error
= push_commit(walk
, git_object_id(revspec
.to
), 0, false);
213 git_object_free(revspec
.from
);
214 git_object_free(revspec
.to
);
218 int git_revwalk_hide_ref(git_revwalk
*walk
, const char *refname
)
220 assert(walk
&& refname
);
221 return push_ref(walk
, refname
, 1, false);
224 static int revwalk_enqueue_timesort(git_revwalk
*walk
, git_commit_list_node
*commit
)
226 return git_pqueue_insert(&walk
->iterator_time
, commit
);
229 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, git_commit_list_node
*commit
)
231 return git_commit_list_insert(commit
, &walk
->iterator_rand
) ? 0 : -1;
234 static int revwalk_next_timesort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
236 git_commit_list_node
*next
;
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
) {
250 static int revwalk_next_unsorted(git_commit_list_node
**object_out
, git_revwalk
*walk
)
253 git_commit_list_node
*next
;
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
) {
266 static int revwalk_next_toposort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
269 git_commit_list_node
*next
;
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
) {
282 static int revwalk_next_reverse(git_commit_list_node
**object_out
, git_revwalk
*walk
)
284 *object_out
= git_commit_list_pop(&walk
->iterator_reverse
);
285 return *object_out
? 0 : GIT_ITEROVER
;
288 static void mark_parents_uninteresting(git_commit_list_node
*commit
)
291 git_commit_list
*parents
= NULL
;
293 for (i
= 0; i
< commit
->out_degree
; i
++)
294 git_commit_list_insert(commit
->parents
[i
], &parents
);
298 commit
= git_commit_list_pop(&parents
);
301 if (commit
->uninteresting
)
304 commit
->uninteresting
= 1;
306 * If we've reached this commit some other way
307 * already, we need to mark its parents uninteresting
310 if (!commit
->parents
)
313 for (i
= 0; i
< commit
->out_degree
; i
++)
314 git_commit_list_insert(commit
->parents
[i
], &parents
);
315 commit
= commit
->parents
[0];
320 static int add_parents_to_list(git_revwalk
*walk
, git_commit_list_node
*commit
, git_commit_list
**list
)
331 * Go full on in the uninteresting case as we want to include
332 * as many of these as we can.
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.
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;
343 /* git does it gently here, but we don't like missing objects */
344 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
348 mark_parents_uninteresting(p
);
351 git_commit_list_insert_by_date(p
, list
);
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.
362 for (i
= 0; i
< commit
->out_degree
; i
++) {
363 git_commit_list_node
*p
= commit
->parents
[i
];
365 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
368 if (walk
->hide_cb
&& walk
->hide_cb(&p
->oid
, walk
->hide_cb_payload
))
373 git_commit_list_insert_by_date(p
, list
);
376 if (walk
->first_parent
)
382 /* How many unintersting commits we want to look at after we run out of interesting ones */
385 static int still_interesting(git_commit_list
*list
, int64_t time
, int slop
)
387 /* The empty list is pretty boring */
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.
395 if (time
<= list
->item
->time
)
398 for (; list
; list
= list
->next
) {
400 * If the destination list still contains interesting commits we
401 * want to continue looking.
403 if (!list
->item
->uninteresting
|| list
->item
->time
> time
)
407 /* Everything's uninteresting, reduce the count */
411 static int limit_list(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*commits
)
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
;
420 git_commit_list_node
*commit
= git_commit_list_pop(&list
);
422 if ((error
= add_parents_to_list(walk
, commit
, &list
)) < 0)
425 if (commit
->uninteresting
) {
426 mark_parents_uninteresting(commit
);
428 slop
= still_interesting(list
, time
, slop
);
435 if (walk
->hide_cb
&& walk
->hide_cb(&commit
->oid
, walk
->hide_cb_payload
))
439 p
= &git_commit_list_insert(commit
, p
)->next
;
442 git_commit_list_free(&list
);
447 static int get_revision(git_commit_list_node
**out
, git_revwalk
*walk
, git_commit_list
**list
)
450 git_commit_list_node
*commit
;
452 commit
= git_commit_list_pop(list
);
459 * If we did not run limit_list and we must add parents to the
462 if (!walk
->limited
) {
463 if ((error
= add_parents_to_list(walk
, commit
, list
)) < 0)
471 static int sort_in_topological_order(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*list
)
473 git_commit_list
*ll
= NULL
, *newlist
, **pptr
;
474 git_commit_list_node
*next
;
476 git_vector_cmp queue_cmp
= NULL
;
480 if (walk
->sorting
& GIT_SORT_TIME
)
481 queue_cmp
= git_commit_list_time_cmp
;
483 if ((error
= git_pqueue_init(&queue
, 0, 8, queue_cmp
)))
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
492 for (ll
= list
; ll
; ll
= ll
->next
) {
493 ll
->item
->in_degree
= 1;
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.
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
)
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.
513 for(ll
= list
; ll
; ll
= ll
->next
) {
514 if (ll
->item
->in_degree
== 1) {
515 if ((error
= git_pqueue_insert(&queue
, ll
->item
)))
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.
525 if ((walk
->sorting
& GIT_SORT_TIME
) == 0)
526 git_pqueue_reverse(&queue
);
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)
537 if (--parent
->in_degree
== 1) {
538 if ((error
= git_pqueue_insert(&queue
, parent
)))
543 /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
546 pptr
= &git_commit_list_insert(next
, pptr
)->next
;
553 git_pqueue_free(&queue
);
557 static int prepare_walk(git_revwalk
*walk
)
560 git_commit_list
*list
, *commits
= NULL
;
561 git_commit_list_node
*next
;
563 /* If there were no pushes, we know that the walk is already over */
564 if (!walk
->did_push
) {
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)
574 if (commit
->uninteresting
)
575 mark_parents_uninteresting(commit
);
579 git_commit_list_insert(commit
, &commits
);
583 if (walk
->limited
&& (error
= limit_list(&commits
, walk
, commits
)) < 0)
586 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
587 error
= sort_in_topological_order(&walk
->iterator_topo
, walk
, commits
);
588 git_commit_list_free(&commits
);
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
);
598 git_commit_list_free(&commits
);
603 walk
->iterator_rand
= commits
;
604 walk
->get_next
= revwalk_next_unsorted
;
607 if (walk
->sorting
& GIT_SORT_REVERSE
) {
609 while ((error
= walk
->get_next(&next
, walk
)) == 0)
610 if (git_commit_list_insert(next
, &walk
->iterator_reverse
) == NULL
)
613 if (error
!= GIT_ITEROVER
)
616 walk
->get_next
= &revwalk_next_reverse
;
624 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
626 git_revwalk
*walk
= git__calloc(1, sizeof(git_revwalk
));
627 GIT_ERROR_CHECK_ALLOC(walk
);
629 walk
->commits
= git_oidmap_alloc();
630 GIT_ERROR_CHECK_ALLOC(walk
->commits
);
632 if (git_pqueue_init(&walk
->iterator_time
, 0, 8, git_commit_list_time_cmp
) < 0)
635 git_pool_init(&walk
->commit_pool
, COMMIT_ALLOC
);
636 walk
->get_next
= &revwalk_next_unsorted
;
637 walk
->enqueue
= &revwalk_enqueue_unsorted
;
641 if (git_repository_odb(&walk
->odb
, repo
) < 0) {
642 git_revwalk_free(walk
);
650 void git_revwalk_free(git_revwalk
*walk
)
655 git_revwalk_reset(walk
);
656 git_odb_free(walk
->odb
);
658 git_oidmap_free(walk
->commits
);
659 git_pool_clear(&walk
->commit_pool
);
660 git_pqueue_free(&walk
->iterator_time
);
664 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
670 void git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
675 git_revwalk_reset(walk
);
677 walk
->sorting
= sort_mode
;
679 if (walk
->sorting
& GIT_SORT_TIME
) {
680 walk
->get_next
= &revwalk_next_timesort
;
681 walk
->enqueue
= &revwalk_enqueue_timesort
;
683 walk
->get_next
= &revwalk_next_unsorted
;
684 walk
->enqueue
= &revwalk_enqueue_unsorted
;
687 if (walk
->sorting
!= GIT_SORT_NONE
)
691 void git_revwalk_simplify_first_parent(git_revwalk
*walk
)
693 walk
->first_parent
= 1;
696 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
699 git_commit_list_node
*next
;
703 if (!walk
->walking
) {
704 if ((error
= prepare_walk(walk
)) < 0)
708 error
= walk
->get_next(&next
, walk
);
710 if (error
== GIT_ITEROVER
) {
711 git_revwalk_reset(walk
);
717 git_oid_cpy(oid
, &next
->oid
);
722 void git_revwalk_reset(git_revwalk
*walk
)
724 git_commit_list_node
*commit
;
728 git_oidmap_foreach_value(walk
->commits
, commit
, {
730 commit
->in_degree
= 0;
731 commit
->topo_delay
= 0;
732 commit
->uninteresting
= 0;
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;
745 walk
->did_push
= walk
->did_hide
= 0;
746 walk
->sorting
= GIT_SORT_NONE
;
749 int git_revwalk_add_hide_cb(
751 git_revwalk_hide_cb hide_cb
,
757 git_revwalk_reset(walk
);
759 walk
->hide_cb
= hide_cb
;
760 walk
->hide_cb_payload
= payload
;