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
;
104 return git_revwalk__push_commit(walk
, oid
, &opts
);
108 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
110 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
113 opts
.uninteresting
= 1;
114 return git_revwalk__push_commit(walk
, oid
, &opts
);
117 int git_revwalk__push_ref(git_revwalk
*walk
, const char *refname
, const git_revwalk__push_options
*opts
)
121 if (git_reference_name_to_id(&oid
, walk
->repo
, refname
) < 0)
124 return git_revwalk__push_commit(walk
, &oid
, opts
);
127 int git_revwalk__push_glob(git_revwalk
*walk
, const char *glob
, const git_revwalk__push_options
*given_opts
)
129 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
131 git_buf buf
= GIT_BUF_INIT
;
133 git_reference_iterator
*iter
;
136 assert(walk
&& glob
);
139 memcpy(&opts
, given_opts
, sizeof(opts
));
141 /* refs/ is implied if not given in the glob */
142 if (git__prefixcmp(glob
, GIT_REFS_DIR
) != 0)
143 git_buf_joinpath(&buf
, GIT_REFS_DIR
, glob
);
145 git_buf_puts(&buf
, glob
);
146 GIT_ERROR_CHECK_ALLOC_BUF(&buf
);
148 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
149 wildcard
= strcspn(glob
, "?*[");
151 git_buf_put(&buf
, "/*", 2);
153 if ((error
= git_reference_iterator_glob_new(&iter
, walk
->repo
, buf
.ptr
)) < 0)
156 opts
.from_glob
= true;
157 while ((error
= git_reference_next(&ref
, iter
)) == 0) {
158 error
= git_revwalk__push_ref(walk
, git_reference_name(ref
), &opts
);
159 git_reference_free(ref
);
163 git_reference_iterator_free(iter
);
165 if (error
== GIT_ITEROVER
)
168 git_buf_dispose(&buf
);
172 int git_revwalk_push_glob(git_revwalk
*walk
, const char *glob
)
174 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
175 assert(walk
&& glob
);
177 return git_revwalk__push_glob(walk
, glob
, &opts
);
180 int git_revwalk_hide_glob(git_revwalk
*walk
, const char *glob
)
182 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
183 assert(walk
&& glob
);
185 opts
.uninteresting
= 1;
186 return git_revwalk__push_glob(walk
, glob
, &opts
);
189 int git_revwalk_push_head(git_revwalk
*walk
)
191 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
194 return git_revwalk__push_ref(walk
, GIT_HEAD_FILE
, &opts
);
197 int git_revwalk_hide_head(git_revwalk
*walk
)
199 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
202 opts
.uninteresting
= 1;
203 return git_revwalk__push_ref(walk
, GIT_HEAD_FILE
, &opts
);
206 int git_revwalk_push_ref(git_revwalk
*walk
, const char *refname
)
208 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
209 assert(walk
&& refname
);
211 return git_revwalk__push_ref(walk
, refname
, &opts
);
214 int git_revwalk_push_range(git_revwalk
*walk
, const char *range
)
216 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
220 if ((error
= git_revparse(&revspec
, walk
->repo
, range
)))
224 git_error_set(GIT_ERROR_INVALID
, "invalid revspec: range not provided");
225 error
= GIT_EINVALIDSPEC
;
229 if (revspec
.flags
& GIT_REVPARSE_MERGE_BASE
) {
230 /* TODO: support "<commit>...<commit>" */
231 git_error_set(GIT_ERROR_INVALID
, "symmetric differences not implemented in revwalk");
232 error
= GIT_EINVALIDSPEC
;
236 opts
.uninteresting
= 1;
237 if ((error
= git_revwalk__push_commit(walk
, git_object_id(revspec
.from
), &opts
)))
240 opts
.uninteresting
= 0;
241 error
= git_revwalk__push_commit(walk
, git_object_id(revspec
.to
), &opts
);
244 git_object_free(revspec
.from
);
245 git_object_free(revspec
.to
);
249 int git_revwalk_hide_ref(git_revwalk
*walk
, const char *refname
)
251 git_revwalk__push_options opts
= GIT_REVWALK__PUSH_OPTIONS_INIT
;
252 assert(walk
&& refname
);
253 opts
.uninteresting
= 1;
254 return git_revwalk__push_ref(walk
, refname
, &opts
);
257 static int revwalk_enqueue_timesort(git_revwalk
*walk
, git_commit_list_node
*commit
)
259 return git_pqueue_insert(&walk
->iterator_time
, commit
);
262 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, git_commit_list_node
*commit
)
264 return git_commit_list_insert(commit
, &walk
->iterator_rand
) ? 0 : -1;
267 static int revwalk_next_timesort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
269 git_commit_list_node
*next
;
271 while ((next
= git_pqueue_pop(&walk
->iterator_time
)) != NULL
) {
272 /* Some commits might become uninteresting after being added to the list */
273 if (!next
->uninteresting
) {
283 static int revwalk_next_unsorted(git_commit_list_node
**object_out
, git_revwalk
*walk
)
286 git_commit_list_node
*next
;
288 while (!(error
= get_revision(&next
, walk
, &walk
->iterator_rand
))) {
289 /* Some commits might become uninteresting after being added to the list */
290 if (!next
->uninteresting
) {
299 static int revwalk_next_toposort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
302 git_commit_list_node
*next
;
304 while (!(error
= get_revision(&next
, walk
, &walk
->iterator_topo
))) {
305 /* Some commits might become uninteresting after being added to the list */
306 if (!next
->uninteresting
) {
315 static int revwalk_next_reverse(git_commit_list_node
**object_out
, git_revwalk
*walk
)
317 *object_out
= git_commit_list_pop(&walk
->iterator_reverse
);
318 return *object_out
? 0 : GIT_ITEROVER
;
321 static void mark_parents_uninteresting(git_commit_list_node
*commit
)
324 git_commit_list
*parents
= NULL
;
326 for (i
= 0; i
< commit
->out_degree
; i
++)
327 git_commit_list_insert(commit
->parents
[i
], &parents
);
331 commit
= git_commit_list_pop(&parents
);
334 if (commit
->uninteresting
)
337 commit
->uninteresting
= 1;
339 * If we've reached this commit some other way
340 * already, we need to mark its parents uninteresting
343 if (!commit
->parents
)
346 for (i
= 0; i
< commit
->out_degree
; i
++)
347 git_commit_list_insert(commit
->parents
[i
], &parents
);
348 commit
= commit
->parents
[0];
353 static int add_parents_to_list(git_revwalk
*walk
, git_commit_list_node
*commit
, git_commit_list
**list
)
364 * Go full on in the uninteresting case as we want to include
365 * as many of these as we can.
367 * Usually we haven't parsed the parent of a parent, but if we
368 * have it we reached it via other means so we want to mark
369 * its parents recursively too.
371 if (commit
->uninteresting
) {
372 for (i
= 0; i
< commit
->out_degree
; i
++) {
373 git_commit_list_node
*p
= commit
->parents
[i
];
374 p
->uninteresting
= 1;
376 /* git does it gently here, but we don't like missing objects */
377 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
381 mark_parents_uninteresting(p
);
384 git_commit_list_insert_by_date(p
, list
);
391 * Now on to what we do if the commit is indeed
392 * interesting. Here we do want things like first-parent take
393 * effect as this is what we'll be showing.
395 for (i
= 0; i
< commit
->out_degree
; i
++) {
396 git_commit_list_node
*p
= commit
->parents
[i
];
398 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
401 if (walk
->hide_cb
&& walk
->hide_cb(&p
->oid
, walk
->hide_cb_payload
))
406 git_commit_list_insert_by_date(p
, list
);
409 if (walk
->first_parent
)
415 /* How many unintersting commits we want to look at after we run out of interesting ones */
418 static int still_interesting(git_commit_list
*list
, int64_t time
, int slop
)
420 /* The empty list is pretty boring */
425 * If the destination list has commits with an earlier date than our
426 * source, we want to reset the slop counter as we're not done.
428 if (time
<= list
->item
->time
)
431 for (; list
; list
= list
->next
) {
433 * If the destination list still contains interesting commits we
434 * want to continue looking.
436 if (!list
->item
->uninteresting
|| list
->item
->time
> time
)
440 /* Everything's uninteresting, reduce the count */
444 static int limit_list(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*commits
)
446 int error
, slop
= SLOP
;
447 int64_t time
= INT64_MAX
;
448 git_commit_list
*list
= commits
;
449 git_commit_list
*newlist
= NULL
;
450 git_commit_list
**p
= &newlist
;
453 git_commit_list_node
*commit
= git_commit_list_pop(&list
);
455 if ((error
= add_parents_to_list(walk
, commit
, &list
)) < 0)
458 if (commit
->uninteresting
) {
459 mark_parents_uninteresting(commit
);
461 slop
= still_interesting(list
, time
, slop
);
468 if (walk
->hide_cb
&& walk
->hide_cb(&commit
->oid
, walk
->hide_cb_payload
))
472 p
= &git_commit_list_insert(commit
, p
)->next
;
475 git_commit_list_free(&list
);
480 static int get_revision(git_commit_list_node
**out
, git_revwalk
*walk
, git_commit_list
**list
)
483 git_commit_list_node
*commit
;
485 commit
= git_commit_list_pop(list
);
492 * If we did not run limit_list and we must add parents to the
495 if (!walk
->limited
) {
496 if ((error
= add_parents_to_list(walk
, commit
, list
)) < 0)
504 static int sort_in_topological_order(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*list
)
506 git_commit_list
*ll
= NULL
, *newlist
, **pptr
;
507 git_commit_list_node
*next
;
509 git_vector_cmp queue_cmp
= NULL
;
513 if (walk
->sorting
& GIT_SORT_TIME
)
514 queue_cmp
= git_commit_list_time_cmp
;
516 if ((error
= git_pqueue_init(&queue
, 0, 8, queue_cmp
)))
520 * Start by resetting the in-degree to 1 for the commits in
521 * our list. We want to go through this list again, so we
522 * store it in the commit list as we extract it from the lower
525 for (ll
= list
; ll
; ll
= ll
->next
) {
526 ll
->item
->in_degree
= 1;
530 * Count up how many children each commit has. We limit
531 * ourselves to those commits in the original list (in-degree
532 * of 1) avoiding setting it for any parent that was hidden.
534 for(ll
= list
; ll
; ll
= ll
->next
) {
535 for (i
= 0; i
< ll
->item
->out_degree
; ++i
) {
536 git_commit_list_node
*parent
= ll
->item
->parents
[i
];
537 if (parent
->in_degree
)
543 * Now we find the tips i.e. those not reachable from any other node
544 * i.e. those which still have an in-degree of 1.
546 for(ll
= list
; ll
; ll
= ll
->next
) {
547 if (ll
->item
->in_degree
== 1) {
548 if ((error
= git_pqueue_insert(&queue
, ll
->item
)))
554 * We need to output the tips in the order that they came out of the
555 * traversal, so if we're not doing time-sorting, we need to reverse the
556 * pqueue in order to get them to come out as we inserted them.
558 if ((walk
->sorting
& GIT_SORT_TIME
) == 0)
559 git_pqueue_reverse(&queue
);
564 while ((next
= git_pqueue_pop(&queue
)) != NULL
) {
565 for (i
= 0; i
< next
->out_degree
; ++i
) {
566 git_commit_list_node
*parent
= next
->parents
[i
];
567 if (parent
->in_degree
== 0)
570 if (--parent
->in_degree
== 1) {
571 if ((error
= git_pqueue_insert(&queue
, parent
)))
576 /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
579 pptr
= &git_commit_list_insert(next
, pptr
)->next
;
586 git_pqueue_free(&queue
);
590 static int prepare_walk(git_revwalk
*walk
)
593 git_commit_list
*list
, *commits
= NULL
;
594 git_commit_list_node
*next
;
596 /* If there were no pushes, we know that the walk is already over */
597 if (!walk
->did_push
) {
602 for (list
= walk
->user_input
; list
; list
= list
->next
) {
603 git_commit_list_node
*commit
= list
->item
;
604 if ((error
= git_commit_list_parse(walk
, commit
)) < 0)
607 if (commit
->uninteresting
)
608 mark_parents_uninteresting(commit
);
612 git_commit_list_insert(commit
, &commits
);
616 if (walk
->limited
&& (error
= limit_list(&commits
, walk
, commits
)) < 0)
619 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
620 error
= sort_in_topological_order(&walk
->iterator_topo
, walk
, commits
);
621 git_commit_list_free(&commits
);
626 walk
->get_next
= &revwalk_next_toposort
;
627 } else if (walk
->sorting
& GIT_SORT_TIME
) {
628 for (list
= commits
; list
&& !error
; list
= list
->next
)
629 error
= walk
->enqueue(walk
, list
->item
);
631 git_commit_list_free(&commits
);
636 walk
->iterator_rand
= commits
;
637 walk
->get_next
= revwalk_next_unsorted
;
640 if (walk
->sorting
& GIT_SORT_REVERSE
) {
642 while ((error
= walk
->get_next(&next
, walk
)) == 0)
643 if (git_commit_list_insert(next
, &walk
->iterator_reverse
) == NULL
)
646 if (error
!= GIT_ITEROVER
)
649 walk
->get_next
= &revwalk_next_reverse
;
657 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
659 git_revwalk
*walk
= git__calloc(1, sizeof(git_revwalk
));
660 GIT_ERROR_CHECK_ALLOC(walk
);
662 if (git_oidmap_new(&walk
->commits
) < 0 ||
663 git_pqueue_init(&walk
->iterator_time
, 0, 8, git_commit_list_time_cmp
) < 0 ||
664 git_pool_init(&walk
->commit_pool
, COMMIT_ALLOC
) < 0)
667 walk
->get_next
= &revwalk_next_unsorted
;
668 walk
->enqueue
= &revwalk_enqueue_unsorted
;
672 if (git_repository_odb(&walk
->odb
, repo
) < 0) {
673 git_revwalk_free(walk
);
681 void git_revwalk_free(git_revwalk
*walk
)
686 git_revwalk_reset(walk
);
687 git_odb_free(walk
->odb
);
689 git_oidmap_free(walk
->commits
);
690 git_pool_clear(&walk
->commit_pool
);
691 git_pqueue_free(&walk
->iterator_time
);
695 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
701 int git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
706 git_revwalk_reset(walk
);
708 walk
->sorting
= sort_mode
;
710 if (walk
->sorting
& GIT_SORT_TIME
) {
711 walk
->get_next
= &revwalk_next_timesort
;
712 walk
->enqueue
= &revwalk_enqueue_timesort
;
714 walk
->get_next
= &revwalk_next_unsorted
;
715 walk
->enqueue
= &revwalk_enqueue_unsorted
;
718 if (walk
->sorting
!= GIT_SORT_NONE
)
724 int git_revwalk_simplify_first_parent(git_revwalk
*walk
)
726 walk
->first_parent
= 1;
730 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
733 git_commit_list_node
*next
;
737 if (!walk
->walking
) {
738 if ((error
= prepare_walk(walk
)) < 0)
742 error
= walk
->get_next(&next
, walk
);
744 if (error
== GIT_ITEROVER
) {
745 git_revwalk_reset(walk
);
751 git_oid_cpy(oid
, &next
->oid
);
756 int git_revwalk_reset(git_revwalk
*walk
)
758 git_commit_list_node
*commit
;
762 git_oidmap_foreach_value(walk
->commits
, commit
, {
764 commit
->in_degree
= 0;
765 commit
->topo_delay
= 0;
766 commit
->uninteresting
= 0;
771 git_pqueue_clear(&walk
->iterator_time
);
772 git_commit_list_free(&walk
->iterator_topo
);
773 git_commit_list_free(&walk
->iterator_rand
);
774 git_commit_list_free(&walk
->iterator_reverse
);
775 git_commit_list_free(&walk
->user_input
);
776 walk
->first_parent
= 0;
779 walk
->did_push
= walk
->did_hide
= 0;
780 walk
->sorting
= GIT_SORT_NONE
;
785 int git_revwalk_add_hide_cb(
787 git_revwalk_hide_cb hide_cb
,
793 git_revwalk_reset(walk
);
795 walk
->hide_cb
= hide_cb
;
796 walk
->hide_cb_payload
= payload
;