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
;
25 /* lookup and reserve space if not already present */
26 if ((commit
= git_oidmap_get(walk
->commits
, oid
)) != NULL
)
29 commit
= git_commit_list_alloc_node(walk
);
33 git_oid_cpy(&commit
->oid
, oid
);
35 if ((git_oidmap_set(walk
->commits
, &commit
->oid
, commit
)) < 0)
41 int git_revwalk__push_commit(git_revwalk
*walk
, const git_oid
*oid
, const git_revwalk__push_options
*opts
)
45 git_object
*obj
, *oobj
;
46 git_commit_list_node
*commit
;
47 git_commit_list
*list
;
49 if ((error
= git_object_lookup(&oobj
, walk
->repo
, oid
, GIT_OBJECT_ANY
)) < 0)
52 error
= git_object_peel(&obj
, oobj
, GIT_OBJECT_COMMIT
);
53 git_object_free(oobj
);
55 if (error
== GIT_ENOTFOUND
|| error
== GIT_EINVALIDSPEC
|| error
== GIT_EPEEL
) {
56 /* If this comes from e.g. push_glob("tags"), ignore this */
60 git_error_set(GIT_ERROR_INVALID
, "object is not a committish");
66 git_oid_cpy(&commit_id
, git_object_id(obj
));
69 commit
= git_revwalk__commit_lookup(walk
, &commit_id
);
71 return -1; /* error already reported by failed lookup */
73 /* A previous hide already told us we don't want this commit */
74 if (commit
->uninteresting
)
77 if (opts
->uninteresting
) {
84 commit
->uninteresting
= opts
->uninteresting
;
85 list
= walk
->user_input
;
86 if ((opts
->insert_by_date
&&
87 git_commit_list_insert_by_date(commit
, &list
) == NULL
) ||
88 git_commit_list_insert(commit
, &list
) == NULL
) {
93 walk
->user_input
= list
;
98 int git_revwalk_push(git_revwalk
*walk
, const git_oid
*oid
)
100 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
102 GIT_ASSERT_ARG(walk
);
105 return git_revwalk__push_commit(walk
, oid
, &opts
);
109 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
111 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
113 GIT_ASSERT_ARG(walk
);
116 opts
.uninteresting
= 1;
117 return git_revwalk__push_commit(walk
, oid
, &opts
);
120 int git_revwalk__push_ref(git_revwalk
*walk
, const char *refname
, const git_revwalk__push_options
*opts
)
124 if (git_reference_name_to_id(&oid
, walk
->repo
, refname
) < 0)
127 return git_revwalk__push_commit(walk
, &oid
, opts
);
130 int git_revwalk__push_glob(git_revwalk
*walk
, const char *glob
, const git_revwalk__push_options
*given_opts
)
132 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
134 git_str buf
= GIT_STR_INIT
;
136 git_reference_iterator
*iter
;
139 GIT_ASSERT_ARG(walk
);
140 GIT_ASSERT_ARG(glob
);
143 memcpy(&opts
, given_opts
, sizeof(opts
));
145 /* refs/ is implied if not given in the glob */
146 if (git__prefixcmp(glob
, GIT_REFS_DIR
) != 0)
147 git_str_joinpath(&buf
, GIT_REFS_DIR
, glob
);
149 git_str_puts(&buf
, glob
);
150 GIT_ERROR_CHECK_ALLOC_STR(&buf
);
152 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
153 wildcard
= strcspn(glob
, "?*[");
155 git_str_put(&buf
, "/*", 2);
157 if ((error
= git_reference_iterator_glob_new(&iter
, walk
->repo
, buf
.ptr
)) < 0)
160 opts
.from_glob
= true;
161 while ((error
= git_reference_next(&ref
, iter
)) == 0) {
162 error
= git_revwalk__push_ref(walk
, git_reference_name(ref
), &opts
);
163 git_reference_free(ref
);
167 git_reference_iterator_free(iter
);
169 if (error
== GIT_ITEROVER
)
172 git_str_dispose(&buf
);
176 int git_revwalk_push_glob(git_revwalk
*walk
, const char *glob
)
178 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
180 GIT_ASSERT_ARG(walk
);
181 GIT_ASSERT_ARG(glob
);
183 return git_revwalk__push_glob(walk
, glob
, &opts
);
186 int git_revwalk_hide_glob(git_revwalk
*walk
, const char *glob
)
188 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
190 GIT_ASSERT_ARG(walk
);
191 GIT_ASSERT_ARG(glob
);
193 opts
.uninteresting
= 1;
194 return git_revwalk__push_glob(walk
, glob
, &opts
);
197 int git_revwalk_push_head(git_revwalk
*walk
)
199 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
201 GIT_ASSERT_ARG(walk
);
203 return git_revwalk__push_ref(walk
, GIT_HEAD_FILE
, &opts
);
206 int git_revwalk_hide_head(git_revwalk
*walk
)
208 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
210 GIT_ASSERT_ARG(walk
);
212 opts
.uninteresting
= 1;
213 return git_revwalk__push_ref(walk
, GIT_HEAD_FILE
, &opts
);
216 int git_revwalk_push_ref(git_revwalk
*walk
, const char *refname
)
218 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
220 GIT_ASSERT_ARG(walk
);
221 GIT_ASSERT_ARG(refname
);
223 return git_revwalk__push_ref(walk
, refname
, &opts
);
226 int git_revwalk_push_range(git_revwalk
*walk
, const char *range
)
228 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
232 if ((error
= git_revparse(&revspec
, walk
->repo
, range
)))
236 git_error_set(GIT_ERROR_INVALID
, "invalid revspec: range not provided");
237 error
= GIT_EINVALIDSPEC
;
241 if (revspec
.flags
& GIT_REVSPEC_MERGE_BASE
) {
242 /* TODO: support "<commit>...<commit>" */
243 git_error_set(GIT_ERROR_INVALID
, "symmetric differences not implemented in revwalk");
244 error
= GIT_EINVALIDSPEC
;
248 opts
.uninteresting
= 1;
249 if ((error
= git_revwalk__push_commit(walk
, git_object_id(revspec
.from
), &opts
)))
252 opts
.uninteresting
= 0;
253 error
= git_revwalk__push_commit(walk
, git_object_id(revspec
.to
), &opts
);
256 git_object_free(revspec
.from
);
257 git_object_free(revspec
.to
);
261 int git_revwalk_hide_ref(git_revwalk
*walk
, const char *refname
)
263 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
265 GIT_ASSERT_ARG(walk
);
266 GIT_ASSERT_ARG(refname
);
268 opts
.uninteresting
= 1;
269 return git_revwalk__push_ref(walk
, refname
, &opts
);
272 static int revwalk_enqueue_timesort(git_revwalk
*walk
, git_commit_list_node
*commit
)
274 return git_pqueue_insert(&walk
->iterator_time
, commit
);
277 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, git_commit_list_node
*commit
)
279 return git_commit_list_insert(commit
, &walk
->iterator_rand
) ? 0 : -1;
282 static int revwalk_next_timesort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
284 git_commit_list_node
*next
;
286 while ((next
= git_pqueue_pop(&walk
->iterator_time
)) != NULL
) {
287 /* Some commits might become uninteresting after being added to the list */
288 if (!next
->uninteresting
) {
298 static int revwalk_next_unsorted(git_commit_list_node
**object_out
, git_revwalk
*walk
)
301 git_commit_list_node
*next
;
303 while (!(error
= get_revision(&next
, walk
, &walk
->iterator_rand
))) {
304 /* Some commits might become uninteresting after being added to the list */
305 if (!next
->uninteresting
) {
314 static int revwalk_next_toposort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
317 git_commit_list_node
*next
;
319 while (!(error
= get_revision(&next
, walk
, &walk
->iterator_topo
))) {
320 /* Some commits might become uninteresting after being added to the list */
321 if (!next
->uninteresting
) {
330 static int revwalk_next_reverse(git_commit_list_node
**object_out
, git_revwalk
*walk
)
332 *object_out
= git_commit_list_pop(&walk
->iterator_reverse
);
333 return *object_out
? 0 : GIT_ITEROVER
;
336 static void mark_parents_uninteresting(git_commit_list_node
*commit
)
339 git_commit_list
*parents
= NULL
;
341 for (i
= 0; i
< commit
->out_degree
; i
++)
342 git_commit_list_insert(commit
->parents
[i
], &parents
);
346 commit
= git_commit_list_pop(&parents
);
349 if (commit
->uninteresting
)
352 commit
->uninteresting
= 1;
354 * If we've reached this commit some other way
355 * already, we need to mark its parents uninteresting
358 if (!commit
->parents
)
361 for (i
= 0; i
< commit
->out_degree
; i
++)
362 git_commit_list_insert(commit
->parents
[i
], &parents
);
363 commit
= commit
->parents
[0];
368 static int add_parents_to_list(git_revwalk
*walk
, git_commit_list_node
*commit
, git_commit_list
**list
)
379 * Go full on in the uninteresting case as we want to include
380 * as many of these as we can.
382 * Usually we haven't parsed the parent of a parent, but if we
383 * have it we reached it via other means so we want to mark
384 * its parents recursively too.
386 if (commit
->uninteresting
) {
387 for (i
= 0; i
< commit
->out_degree
; i
++) {
388 git_commit_list_node
*p
= commit
->parents
[i
];
389 p
->uninteresting
= 1;
391 /* git does it gently here, but we don't like missing objects */
392 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
396 mark_parents_uninteresting(p
);
399 git_commit_list_insert_by_date(p
, list
);
406 * Now on to what we do if the commit is indeed
407 * interesting. Here we do want things like first-parent take
408 * effect as this is what we'll be showing.
410 for (i
= 0; i
< commit
->out_degree
; i
++) {
411 git_commit_list_node
*p
= commit
->parents
[i
];
413 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
416 if (walk
->hide_cb
&& walk
->hide_cb(&p
->oid
, walk
->hide_cb_payload
))
421 git_commit_list_insert_by_date(p
, list
);
424 if (walk
->first_parent
)
430 /* How many uninteresting commits we want to look at after we run out of interesting ones */
433 static int still_interesting(git_commit_list
*list
, int64_t time
, int slop
)
435 /* The empty list is pretty boring */
440 * If the destination list has commits with an earlier date than our
441 * source, we want to reset the slop counter as we're not done.
443 if (time
<= list
->item
->time
)
446 for (; list
; list
= list
->next
) {
448 * If the destination list still contains interesting commits we
449 * want to continue looking.
451 if (!list
->item
->uninteresting
|| list
->item
->time
> time
)
455 /* Everything's uninteresting, reduce the count */
459 static int limit_list(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*commits
)
461 int error
, slop
= SLOP
;
462 int64_t time
= INT64_MAX
;
463 git_commit_list
*list
= commits
;
464 git_commit_list
*newlist
= NULL
;
465 git_commit_list
**p
= &newlist
;
468 git_commit_list_node
*commit
= git_commit_list_pop(&list
);
470 if ((error
= add_parents_to_list(walk
, commit
, &list
)) < 0)
473 if (commit
->uninteresting
) {
474 mark_parents_uninteresting(commit
);
476 slop
= still_interesting(list
, time
, slop
);
483 if (walk
->hide_cb
&& walk
->hide_cb(&commit
->oid
, walk
->hide_cb_payload
))
487 p
= &git_commit_list_insert(commit
, p
)->next
;
490 git_commit_list_free(&list
);
495 static int get_revision(git_commit_list_node
**out
, git_revwalk
*walk
, git_commit_list
**list
)
498 git_commit_list_node
*commit
;
500 commit
= git_commit_list_pop(list
);
507 * If we did not run limit_list and we must add parents to the
510 if (!walk
->limited
) {
511 if ((error
= add_parents_to_list(walk
, commit
, list
)) < 0)
519 static int sort_in_topological_order(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*list
)
521 git_commit_list
*ll
= NULL
, *newlist
, **pptr
;
522 git_commit_list_node
*next
;
524 git_vector_cmp queue_cmp
= NULL
;
528 if (walk
->sorting
& GIT_SORT_TIME
)
529 queue_cmp
= git_commit_list_time_cmp
;
531 if ((error
= git_pqueue_init(&queue
, 0, 8, queue_cmp
)))
535 * Start by resetting the in-degree to 1 for the commits in
536 * our list. We want to go through this list again, so we
537 * store it in the commit list as we extract it from the lower
540 for (ll
= list
; ll
; ll
= ll
->next
) {
541 ll
->item
->in_degree
= 1;
545 * Count up how many children each commit has. We limit
546 * ourselves to those commits in the original list (in-degree
547 * of 1) avoiding setting it for any parent that was hidden.
549 for(ll
= list
; ll
; ll
= ll
->next
) {
550 for (i
= 0; i
< ll
->item
->out_degree
; ++i
) {
551 git_commit_list_node
*parent
= ll
->item
->parents
[i
];
552 if (parent
->in_degree
)
558 * Now we find the tips i.e. those not reachable from any other node
559 * i.e. those which still have an in-degree of 1.
561 for(ll
= list
; ll
; ll
= ll
->next
) {
562 if (ll
->item
->in_degree
== 1) {
563 if ((error
= git_pqueue_insert(&queue
, ll
->item
)))
569 * We need to output the tips in the order that they came out of the
570 * traversal, so if we're not doing time-sorting, we need to reverse the
571 * pqueue in order to get them to come out as we inserted them.
573 if ((walk
->sorting
& GIT_SORT_TIME
) == 0)
574 git_pqueue_reverse(&queue
);
579 while ((next
= git_pqueue_pop(&queue
)) != NULL
) {
580 for (i
= 0; i
< next
->out_degree
; ++i
) {
581 git_commit_list_node
*parent
= next
->parents
[i
];
582 if (parent
->in_degree
== 0)
585 if (--parent
->in_degree
== 1) {
586 if ((error
= git_pqueue_insert(&queue
, parent
)))
591 /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
594 pptr
= &git_commit_list_insert(next
, pptr
)->next
;
601 git_pqueue_free(&queue
);
605 static int prepare_walk(git_revwalk
*walk
)
608 git_commit_list
*list
, *commits
= NULL
;
609 git_commit_list_node
*next
;
611 /* If there were no pushes, we know that the walk is already over */
612 if (!walk
->did_push
) {
617 for (list
= walk
->user_input
; list
; list
= list
->next
) {
618 git_commit_list_node
*commit
= list
->item
;
619 if ((error
= git_commit_list_parse(walk
, commit
)) < 0)
622 if (commit
->uninteresting
)
623 mark_parents_uninteresting(commit
);
627 git_commit_list_insert(commit
, &commits
);
631 if (walk
->limited
&& (error
= limit_list(&commits
, walk
, commits
)) < 0)
634 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
635 error
= sort_in_topological_order(&walk
->iterator_topo
, walk
, commits
);
636 git_commit_list_free(&commits
);
641 walk
->get_next
= &revwalk_next_toposort
;
642 } else if (walk
->sorting
& GIT_SORT_TIME
) {
643 for (list
= commits
; list
&& !error
; list
= list
->next
)
644 error
= walk
->enqueue(walk
, list
->item
);
646 git_commit_list_free(&commits
);
651 walk
->iterator_rand
= commits
;
652 walk
->get_next
= revwalk_next_unsorted
;
655 if (walk
->sorting
& GIT_SORT_REVERSE
) {
657 while ((error
= walk
->get_next(&next
, walk
)) == 0)
658 if (git_commit_list_insert(next
, &walk
->iterator_reverse
) == NULL
)
661 if (error
!= GIT_ITEROVER
)
664 walk
->get_next
= &revwalk_next_reverse
;
672 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
674 git_revwalk
*walk
= git__calloc(1, sizeof(git_revwalk
));
675 GIT_ERROR_CHECK_ALLOC(walk
);
677 if (git_oidmap_new(&walk
->commits
) < 0 ||
678 git_pqueue_init(&walk
->iterator_time
, 0, 8, git_commit_list_time_cmp
) < 0 ||
679 git_pool_init(&walk
->commit_pool
, COMMIT_ALLOC
) < 0)
682 walk
->get_next
= &revwalk_next_unsorted
;
683 walk
->enqueue
= &revwalk_enqueue_unsorted
;
687 if (git_repository_odb(&walk
->odb
, repo
) < 0) {
688 git_revwalk_free(walk
);
696 void git_revwalk_free(git_revwalk
*walk
)
701 git_revwalk_reset(walk
);
702 git_odb_free(walk
->odb
);
704 git_oidmap_free(walk
->commits
);
705 git_pool_clear(&walk
->commit_pool
);
706 git_pqueue_free(&walk
->iterator_time
);
710 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
712 GIT_ASSERT_ARG_WITH_RETVAL(walk
, NULL
);
717 int git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
719 GIT_ASSERT_ARG(walk
);
722 git_revwalk_reset(walk
);
724 walk
->sorting
= sort_mode
;
726 if (walk
->sorting
& GIT_SORT_TIME
) {
727 walk
->get_next
= &revwalk_next_timesort
;
728 walk
->enqueue
= &revwalk_enqueue_timesort
;
730 walk
->get_next
= &revwalk_next_unsorted
;
731 walk
->enqueue
= &revwalk_enqueue_unsorted
;
734 if (walk
->sorting
!= GIT_SORT_NONE
)
740 int git_revwalk_simplify_first_parent(git_revwalk
*walk
)
742 walk
->first_parent
= 1;
746 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
749 git_commit_list_node
*next
;
751 GIT_ASSERT_ARG(walk
);
754 if (!walk
->walking
) {
755 if ((error
= prepare_walk(walk
)) < 0)
759 error
= walk
->get_next(&next
, walk
);
761 if (error
== GIT_ITEROVER
) {
762 git_revwalk_reset(walk
);
768 git_oid_cpy(oid
, &next
->oid
);
773 int git_revwalk_reset(git_revwalk
*walk
)
775 git_commit_list_node
*commit
;
777 GIT_ASSERT_ARG(walk
);
779 git_oidmap_foreach_value(walk
->commits
, commit
, {
781 commit
->in_degree
= 0;
782 commit
->topo_delay
= 0;
783 commit
->uninteresting
= 0;
788 git_pqueue_clear(&walk
->iterator_time
);
789 git_commit_list_free(&walk
->iterator_topo
);
790 git_commit_list_free(&walk
->iterator_rand
);
791 git_commit_list_free(&walk
->iterator_reverse
);
792 git_commit_list_free(&walk
->user_input
);
793 walk
->first_parent
= 0;
796 walk
->did_push
= walk
->did_hide
= 0;
797 walk
->sorting
= GIT_SORT_NONE
;
802 int git_revwalk_add_hide_cb(
804 git_revwalk_hide_cb hide_cb
,
807 GIT_ASSERT_ARG(walk
);
810 git_revwalk_reset(walk
);
812 walk
->hide_cb
= hide_cb
;
813 walk
->hide_cb_payload
= payload
;