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"
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
= kh_get(oid
, walk
->commits
, oid
);
29 if (pos
!= kh_end(walk
->commits
))
30 return kh_value(walk
->commits
, pos
);
32 commit
= git_commit_list_alloc_node(walk
);
36 git_oid_cpy(&commit
->oid
, oid
);
38 pos
= kh_put(oid
, walk
->commits
, &commit
->oid
, &ret
);
40 kh_value(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_OBJ_ANY
)) < 0)
56 error
= git_object_peel(&obj
, oobj
, GIT_OBJ_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 giterr_set(GITERR_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
)
86 commit
->uninteresting
= uninteresting
;
87 list
= walk
->user_input
;
88 if (git_commit_list_insert(commit
, &list
) == NULL
) {
93 walk
->user_input
= list
;
98 int git_revwalk_push(git_revwalk
*walk
, const git_oid
*oid
)
101 return push_commit(walk
, oid
, 0, false);
105 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
108 return push_commit(walk
, oid
, 1, false);
111 static int push_ref(git_revwalk
*walk
, const char *refname
, int hide
, int from_glob
)
115 if (git_reference_name_to_id(&oid
, walk
->repo
, refname
) < 0)
118 return push_commit(walk
, &oid
, hide
, from_glob
);
121 static int push_glob(git_revwalk
*walk
, const char *glob
, int hide
)
124 git_buf buf
= GIT_BUF_INIT
;
126 git_reference_iterator
*iter
;
129 assert(walk
&& glob
);
131 /* refs/ is implied if not given in the glob */
132 if (git__prefixcmp(glob
, GIT_REFS_DIR
) != 0)
133 git_buf_joinpath(&buf
, GIT_REFS_DIR
, glob
);
135 git_buf_puts(&buf
, glob
);
136 GITERR_CHECK_ALLOC_BUF(&buf
);
138 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
139 wildcard
= strcspn(glob
, "?*[");
141 git_buf_put(&buf
, "/*", 2);
143 if ((error
= git_reference_iterator_glob_new(&iter
, walk
->repo
, buf
.ptr
)) < 0)
146 while ((error
= git_reference_next(&ref
, iter
)) == 0) {
147 error
= push_ref(walk
, git_reference_name(ref
), hide
, true);
148 git_reference_free(ref
);
152 git_reference_iterator_free(iter
);
154 if (error
== GIT_ITEROVER
)
161 int git_revwalk_push_glob(git_revwalk
*walk
, const char *glob
)
163 assert(walk
&& glob
);
164 return push_glob(walk
, glob
, 0);
167 int git_revwalk_hide_glob(git_revwalk
*walk
, const char *glob
)
169 assert(walk
&& glob
);
170 return push_glob(walk
, glob
, 1);
173 int git_revwalk_push_head(git_revwalk
*walk
)
176 return push_ref(walk
, GIT_HEAD_FILE
, 0, false);
179 int git_revwalk_hide_head(git_revwalk
*walk
)
182 return push_ref(walk
, GIT_HEAD_FILE
, 1, false);
185 int git_revwalk_push_ref(git_revwalk
*walk
, const char *refname
)
187 assert(walk
&& refname
);
188 return push_ref(walk
, refname
, 0, false);
191 int git_revwalk_push_range(git_revwalk
*walk
, const char *range
)
196 if ((error
= git_revparse(&revspec
, walk
->repo
, range
)))
199 if (revspec
.flags
& GIT_REVPARSE_MERGE_BASE
) {
200 /* TODO: support "<commit>...<commit>" */
201 giterr_set(GITERR_INVALID
, "Symmetric differences not implemented in revwalk");
202 return GIT_EINVALIDSPEC
;
205 if ((error
= push_commit(walk
, git_object_id(revspec
.from
), 1, false)))
208 error
= push_commit(walk
, git_object_id(revspec
.to
), 0, false);
211 git_object_free(revspec
.from
);
212 git_object_free(revspec
.to
);
216 int git_revwalk_hide_ref(git_revwalk
*walk
, const char *refname
)
218 assert(walk
&& refname
);
219 return push_ref(walk
, refname
, 1, false);
222 static int revwalk_enqueue_timesort(git_revwalk
*walk
, git_commit_list_node
*commit
)
224 return git_pqueue_insert(&walk
->iterator_time
, commit
);
227 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, git_commit_list_node
*commit
)
229 return git_commit_list_insert(commit
, &walk
->iterator_rand
) ? 0 : -1;
232 static int revwalk_next_timesort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
234 git_commit_list_node
*next
;
236 if ((next
= git_pqueue_pop(&walk
->iterator_time
)) != NULL
) {
245 static int revwalk_next_unsorted(git_commit_list_node
**object_out
, git_revwalk
*walk
)
247 git_commit_list_node
*next
;
249 if ((next
= git_commit_list_pop(&walk
->iterator_rand
)) != NULL
) {
258 static int revwalk_next_toposort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
260 git_commit_list_node
*node
;
262 node
= git_commit_list_pop(&walk
->iterator_topo
);
272 static int revwalk_next_reverse(git_commit_list_node
**object_out
, git_revwalk
*walk
)
274 *object_out
= git_commit_list_pop(&walk
->iterator_reverse
);
275 return *object_out
? 0 : GIT_ITEROVER
;
278 static void mark_parents_uninteresting(git_commit_list_node
*commit
)
281 git_commit_list
*parents
= NULL
;
283 for (i
= 0; i
< commit
->out_degree
; i
++)
284 git_commit_list_insert(commit
->parents
[i
], &parents
);
288 git_commit_list_node
*commit
= git_commit_list_pop(&parents
);
291 if (commit
->uninteresting
)
294 commit
->uninteresting
= 1;
296 * If we've reached this commit some other way
297 * already, we need to mark its parents uninteresting
300 if (!commit
->parents
)
303 for (i
= 0; i
< commit
->out_degree
; i
++)
304 git_commit_list_insert(commit
->parents
[i
], &parents
);
305 commit
= commit
->parents
[0];
310 static int add_parents_to_list(git_revwalk
*walk
, git_commit_list_node
*commit
, git_commit_list
**list
)
321 * Go full on in the uninteresting case as we want to include
322 * as many of these as we can.
324 * Usually we haven't parsed the parent of a parent, but if we
325 * have it we reached it via other means so we want to mark
326 * its parents recursively too.
328 if (commit
->uninteresting
) {
329 for (i
= 0; i
< commit
->out_degree
; i
++) {
330 git_commit_list_node
*p
= commit
->parents
[i
];
331 p
->uninteresting
= 1;
333 /* git does it gently here, but we don't like missing objects */
334 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
338 mark_parents_uninteresting(p
);
341 git_commit_list_insert_by_date(p
, list
);
348 * Now on to what we do if the commit is indeed
349 * interesting. Here we do want things like first-parent take
350 * effect as this is what we'll be showing.
352 for (i
= 0; i
< commit
->out_degree
; i
++) {
353 git_commit_list_node
*p
= commit
->parents
[i
];
355 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
358 if (walk
->hide_cb
&& walk
->hide_cb(&p
->oid
, walk
->hide_cb_payload
))
363 git_commit_list_insert_by_date(p
, list
);
366 if (walk
->first_parent
)
372 static int everybody_uninteresting(git_commit_list
*orig
)
374 git_commit_list
*list
= orig
;
377 git_commit_list_node
*commit
= list
->item
;
379 if (!commit
->uninteresting
)
386 /* How many unintersting commits we want to look at after we run out of interesting ones */
389 static int still_interesting(git_commit_list
*list
, int64_t time
, int slop
)
391 /* The empty list is pretty boring */
396 * If the destination list has commits with an earlier date
397 * than our source we want to continue looking.
399 if (time
<= list
->item
->time
)
402 /* If we find interesting commits, we reset the slop count */
403 if (!everybody_uninteresting(list
))
406 /* Everything's uninteresting, reduce the count */
410 static int limit_list(git_commit_list
**out
, git_revwalk
*walk
, git_commit_list
*commits
)
412 int error
, slop
= SLOP
;
414 git_commit_list
*list
= commits
;
415 git_commit_list
*newlist
= NULL
;
416 git_commit_list
**p
= &newlist
;
419 git_commit_list_node
*commit
= git_commit_list_pop(&list
);
421 if ((error
= add_parents_to_list(walk
, commit
, &list
)) < 0)
424 if (commit
->uninteresting
) {
425 mark_parents_uninteresting(commit
);
427 slop
= still_interesting(list
, time
, slop
);
434 if (!commit
->uninteresting
&& walk
->hide_cb
&& walk
->hide_cb(&commit
->oid
, walk
->hide_cb_payload
))
438 p
= &git_commit_list_insert(commit
, p
)->next
;
441 git_commit_list_free(&list
);
446 static int sort_in_topological_order(git_commit_list
**out
, git_revwalk
*walk
)
448 git_commit_list
*list
= NULL
, *ll
= NULL
, *newlist
, **pptr
;
449 git_commit_list_node
*next
;
451 git_vector_cmp queue_cmp
= NULL
;
455 if (walk
->sorting
& GIT_SORT_TIME
)
456 queue_cmp
= git_commit_list_time_cmp
;
458 if ((error
= git_pqueue_init(&queue
, 0, 8, queue_cmp
)))
462 * Start by resetting the in-degree to 1 for the commits in
463 * our list. We want to go through this list again, so we
464 * store it in the commit list as we extract it from the lower
467 while ((error
= walk
->get_next(&next
, walk
)) == 0) {
469 git_commit_list_insert(next
, &list
);
472 if (error
!= GIT_ITEROVER
)
478 * Count up how many children each commit has. We limit
479 * ourselves to those commits in the original list (in-degree
480 * of 1) avoiding setting it for any parent that was hidden.
482 for(ll
= list
; ll
; ll
= ll
->next
) {
483 for (i
= 0; i
< ll
->item
->out_degree
; ++i
) {
484 git_commit_list_node
*parent
= ll
->item
->parents
[i
];
485 if (parent
->in_degree
)
491 * Now we find the tips i.e. those not reachable from any other node
492 * i.e. those which still have an in-degree of 1.
494 for(ll
= list
; ll
; ll
= ll
->next
) {
495 if (ll
->item
->in_degree
== 1) {
496 if ((error
= git_pqueue_insert(&queue
, ll
->item
)))
502 * We need to output the tips in the order that they came out of the
503 * traversal, so if we're not doing time-sorting, we need to reverse the
504 * pqueue in order to get them to come out as we inserted them.
506 if ((walk
->sorting
& GIT_SORT_TIME
) == 0)
507 git_pqueue_reverse(&queue
);
512 while ((next
= git_pqueue_pop(&queue
)) != NULL
) {
513 for (i
= 0; i
< next
->out_degree
; ++i
) {
514 git_commit_list_node
*parent
= next
->parents
[i
];
515 if (parent
->in_degree
== 0)
518 if (--parent
->in_degree
== 1) {
519 if ((error
= git_pqueue_insert(&queue
, parent
)))
524 /* All the children of 'item' have been emitted (since we got to it via the priority queue) */
527 pptr
= &git_commit_list_insert(next
, pptr
)->next
;
534 git_commit_list_free(&list
);
535 git_pqueue_free(&queue
);
539 static int prepare_walk(git_revwalk
*walk
)
542 git_commit_list
*list
, *commits
= NULL
;
543 git_commit_list_node
*next
;
545 /* If there were no pushes, we know that the walk is already over */
546 if (!walk
->did_push
) {
551 for (list
= walk
->user_input
; list
; list
= list
->next
) {
552 git_commit_list_node
*commit
= list
->item
;
553 if ((error
= git_commit_list_parse(walk
, commit
)) < 0)
556 if (commit
->uninteresting
)
557 mark_parents_uninteresting(commit
);
561 git_commit_list_insert(commit
, &commits
);
565 if ((error
= limit_list(&commits
, walk
, commits
)) < 0)
568 for (list
= commits
; list
; list
= list
->next
) {
569 if (list
->item
->uninteresting
)
572 if ((error
= walk
->enqueue(walk
, list
->item
)) < 0) {
573 git_commit_list_free(&commits
);
578 git_commit_list_free(&commits
);
580 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
581 if ((error
= sort_in_topological_order(&walk
->iterator_topo
, walk
)))
584 walk
->get_next
= &revwalk_next_toposort
;
587 if (walk
->sorting
& GIT_SORT_REVERSE
) {
589 while ((error
= walk
->get_next(&next
, walk
)) == 0)
590 if (git_commit_list_insert(next
, &walk
->iterator_reverse
) == NULL
)
593 if (error
!= GIT_ITEROVER
)
596 walk
->get_next
= &revwalk_next_reverse
;
604 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
606 git_revwalk
*walk
= git__calloc(1, sizeof(git_revwalk
));
607 GITERR_CHECK_ALLOC(walk
);
609 walk
->commits
= git_oidmap_alloc();
610 GITERR_CHECK_ALLOC(walk
->commits
);
612 if (git_pqueue_init(&walk
->iterator_time
, 0, 8, git_commit_list_time_cmp
) < 0)
615 git_pool_init(&walk
->commit_pool
, COMMIT_ALLOC
);
616 walk
->get_next
= &revwalk_next_unsorted
;
617 walk
->enqueue
= &revwalk_enqueue_unsorted
;
621 if (git_repository_odb(&walk
->odb
, repo
) < 0) {
622 git_revwalk_free(walk
);
630 void git_revwalk_free(git_revwalk
*walk
)
635 git_revwalk_reset(walk
);
636 git_odb_free(walk
->odb
);
638 git_oidmap_free(walk
->commits
);
639 git_pool_clear(&walk
->commit_pool
);
640 git_pqueue_free(&walk
->iterator_time
);
644 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
650 void git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
655 git_revwalk_reset(walk
);
657 walk
->sorting
= sort_mode
;
659 if (walk
->sorting
& GIT_SORT_TIME
) {
660 walk
->get_next
= &revwalk_next_timesort
;
661 walk
->enqueue
= &revwalk_enqueue_timesort
;
663 walk
->get_next
= &revwalk_next_unsorted
;
664 walk
->enqueue
= &revwalk_enqueue_unsorted
;
668 void git_revwalk_simplify_first_parent(git_revwalk
*walk
)
670 walk
->first_parent
= 1;
673 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
676 git_commit_list_node
*next
;
680 if (!walk
->walking
) {
681 if ((error
= prepare_walk(walk
)) < 0)
685 error
= walk
->get_next(&next
, walk
);
687 if (error
== GIT_ITEROVER
) {
688 git_revwalk_reset(walk
);
694 git_oid_cpy(oid
, &next
->oid
);
699 void git_revwalk_reset(git_revwalk
*walk
)
701 git_commit_list_node
*commit
;
705 kh_foreach_value(walk
->commits
, commit
, {
707 commit
->in_degree
= 0;
708 commit
->topo_delay
= 0;
709 commit
->uninteresting
= 0;
714 git_pqueue_clear(&walk
->iterator_time
);
715 git_commit_list_free(&walk
->iterator_topo
);
716 git_commit_list_free(&walk
->iterator_rand
);
717 git_commit_list_free(&walk
->iterator_reverse
);
718 git_commit_list_free(&walk
->user_input
);
719 walk
->first_parent
= 0;
721 walk
->did_push
= walk
->did_hide
= 0;
724 int git_revwalk_add_hide_cb(
726 git_revwalk_hide_cb hide_cb
,
732 git_revwalk_reset(walk
);
735 /* There is already a callback added */
736 giterr_set(GITERR_INVALID
, "There is already a callback added to hide commits in revision walker.");
740 walk
->hide_cb
= hide_cb
;
741 walk
->hide_cb_payload
= payload
;