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"
19 git_commit_list_node
*git_revwalk__commit_lookup(
20 git_revwalk
*walk
, const git_oid
*oid
)
22 git_commit_list_node
*commit
;
26 /* lookup and reserve space if not already present */
27 pos
= kh_get(oid
, walk
->commits
, oid
);
28 if (pos
!= kh_end(walk
->commits
))
29 return kh_value(walk
->commits
, pos
);
31 commit
= git_commit_list_alloc_node(walk
);
35 git_oid_cpy(&commit
->oid
, oid
);
37 pos
= kh_put(oid
, walk
->commits
, &commit
->oid
, &ret
);
39 kh_value(walk
->commits
, pos
) = commit
;
44 typedef git_array_t(git_commit_list_node
*) commit_list_node_array
;
46 static bool interesting_arr(commit_list_node_array arr
)
48 git_commit_list_node
**n
;
51 size
= git_array_size(arr
);
52 for (i
= 0; i
< size
; i
++) {
53 n
= git_array_get(arr
, i
);
57 if (!(*n
)->uninteresting
)
64 static int mark_uninteresting(git_revwalk
*walk
, git_commit_list_node
*commit
)
68 commit_list_node_array pending
= GIT_ARRAY_INIT
;
69 git_commit_list_node
**tmp
;
74 commit
->uninteresting
= 1;
76 if ((error
= git_commit_list_parse(walk
, commit
)) < 0)
79 for (i
= 0; i
< commit
->out_degree
; ++i
)
80 if (!commit
->parents
[i
]->uninteresting
) {
81 git_commit_list_node
**node
= git_array_alloc(pending
);
82 GITERR_CHECK_ALLOC(node
);
83 *node
= commit
->parents
[i
];
86 tmp
= git_array_pop(pending
);
87 commit
= tmp
? *tmp
: NULL
;
89 } while (commit
!= NULL
&& !interesting_arr(pending
));
91 git_array_clear(pending
);
96 static int process_commit(git_revwalk
*walk
, git_commit_list_node
*commit
, int hide
)
100 if (!hide
&& walk
->hide_cb
)
101 hide
= walk
->hide_cb(&commit
->oid
, walk
->hide_cb_payload
);
103 if (hide
&& mark_uninteresting(walk
, commit
) < 0)
111 if ((error
= git_commit_list_parse(walk
, commit
)) < 0)
115 return walk
->enqueue(walk
, commit
);
120 static int process_commit_parents(git_revwalk
*walk
, git_commit_list_node
*commit
)
122 unsigned short i
, max
;
125 max
= commit
->out_degree
;
126 if (walk
->first_parent
&& commit
->out_degree
)
129 for (i
= 0; i
< max
&& !error
; ++i
)
130 error
= process_commit(walk
, commit
->parents
[i
], commit
->uninteresting
);
135 static int push_commit(git_revwalk
*walk
, const git_oid
*oid
, int uninteresting
, int from_glob
)
139 git_object
*obj
, *oobj
;
140 git_commit_list_node
*commit
;
141 git_commit_list
*list
;
143 if ((error
= git_object_lookup(&oobj
, walk
->repo
, oid
, GIT_OBJ_ANY
)) < 0)
146 error
= git_object_peel(&obj
, oobj
, GIT_OBJ_COMMIT
);
147 git_object_free(oobj
);
149 if (error
== GIT_ENOTFOUND
|| error
== GIT_EINVALIDSPEC
|| error
== GIT_EPEEL
) {
150 /* If this comes from e.g. push_glob("tags"), ignore this */
154 giterr_set(GITERR_INVALID
, "Object is not a committish");
160 git_oid_cpy(&commit_id
, git_object_id(obj
));
161 git_object_free(obj
);
163 commit
= git_revwalk__commit_lookup(walk
, &commit_id
);
165 return -1; /* error already reported by failed lookup */
167 /* A previous hide already told us we don't want this commit */
168 if (commit
->uninteresting
)
176 commit
->uninteresting
= uninteresting
;
177 list
= walk
->user_input
;
178 if (git_commit_list_insert(commit
, &list
) == NULL
) {
183 walk
->user_input
= list
;
188 int git_revwalk_push(git_revwalk
*walk
, const git_oid
*oid
)
191 return push_commit(walk
, oid
, 0, false);
195 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
198 return push_commit(walk
, oid
, 1, false);
201 static int push_ref(git_revwalk
*walk
, const char *refname
, int hide
, int from_glob
)
205 if (git_reference_name_to_id(&oid
, walk
->repo
, refname
) < 0)
208 return push_commit(walk
, &oid
, hide
, from_glob
);
211 static int push_glob(git_revwalk
*walk
, const char *glob
, int hide
)
214 git_buf buf
= GIT_BUF_INIT
;
216 git_reference_iterator
*iter
;
219 assert(walk
&& glob
);
221 /* refs/ is implied if not given in the glob */
222 if (git__prefixcmp(glob
, GIT_REFS_DIR
) != 0)
223 git_buf_joinpath(&buf
, GIT_REFS_DIR
, glob
);
225 git_buf_puts(&buf
, glob
);
226 GITERR_CHECK_ALLOC_BUF(&buf
);
228 /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
229 wildcard
= strcspn(glob
, "?*[");
231 git_buf_put(&buf
, "/*", 2);
233 if ((error
= git_reference_iterator_glob_new(&iter
, walk
->repo
, buf
.ptr
)) < 0)
236 while ((error
= git_reference_next(&ref
, iter
)) == 0) {
237 error
= push_ref(walk
, git_reference_name(ref
), hide
, true);
238 git_reference_free(ref
);
242 git_reference_iterator_free(iter
);
244 if (error
== GIT_ITEROVER
)
251 int git_revwalk_push_glob(git_revwalk
*walk
, const char *glob
)
253 assert(walk
&& glob
);
254 return push_glob(walk
, glob
, 0);
257 int git_revwalk_hide_glob(git_revwalk
*walk
, const char *glob
)
259 assert(walk
&& glob
);
260 return push_glob(walk
, glob
, 1);
263 int git_revwalk_push_head(git_revwalk
*walk
)
266 return push_ref(walk
, GIT_HEAD_FILE
, 0, false);
269 int git_revwalk_hide_head(git_revwalk
*walk
)
272 return push_ref(walk
, GIT_HEAD_FILE
, 1, false);
275 int git_revwalk_push_ref(git_revwalk
*walk
, const char *refname
)
277 assert(walk
&& refname
);
278 return push_ref(walk
, refname
, 0, false);
281 int git_revwalk_push_range(git_revwalk
*walk
, const char *range
)
286 if ((error
= git_revparse(&revspec
, walk
->repo
, range
)))
289 if (revspec
.flags
& GIT_REVPARSE_MERGE_BASE
) {
290 /* TODO: support "<commit>...<commit>" */
291 giterr_set(GITERR_INVALID
, "Symmetric differences not implemented in revwalk");
292 return GIT_EINVALIDSPEC
;
295 if ((error
= push_commit(walk
, git_object_id(revspec
.from
), 1, false)))
298 error
= push_commit(walk
, git_object_id(revspec
.to
), 0, false);
301 git_object_free(revspec
.from
);
302 git_object_free(revspec
.to
);
306 int git_revwalk_hide_ref(git_revwalk
*walk
, const char *refname
)
308 assert(walk
&& refname
);
309 return push_ref(walk
, refname
, 1, false);
312 static int revwalk_enqueue_timesort(git_revwalk
*walk
, git_commit_list_node
*commit
)
314 return git_pqueue_insert(&walk
->iterator_time
, commit
);
317 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, git_commit_list_node
*commit
)
319 return git_commit_list_insert(commit
, &walk
->iterator_rand
) ? 0 : -1;
322 static int revwalk_next_timesort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
325 git_commit_list_node
*next
;
327 while ((next
= git_pqueue_pop(&walk
->iterator_time
)) != NULL
)
328 if (!next
->uninteresting
) {
329 if ((error
= process_commit_parents(walk
, next
)) < 0)
340 static int revwalk_next_unsorted(git_commit_list_node
**object_out
, git_revwalk
*walk
)
343 git_commit_list_node
*next
;
345 while ((next
= git_commit_list_pop(&walk
->iterator_rand
)) != NULL
)
346 if (!next
->uninteresting
) {
347 if ((error
= process_commit_parents(walk
, next
)) < 0)
358 static int revwalk_next_toposort(git_commit_list_node
**object_out
, git_revwalk
*walk
)
360 git_commit_list_node
*next
;
361 unsigned short i
, max
;
364 next
= git_commit_list_pop(&walk
->iterator_topo
);
370 if (next
->in_degree
> 0) {
371 next
->topo_delay
= 1;
376 max
= next
->out_degree
;
377 if (walk
->first_parent
&& next
->out_degree
)
380 for (i
= 0; i
< max
; ++i
) {
381 git_commit_list_node
*parent
= next
->parents
[i
];
383 if (--parent
->in_degree
== 0 && parent
->topo_delay
) {
384 parent
->topo_delay
= 0;
385 if (git_commit_list_insert(parent
, &walk
->iterator_topo
) == NULL
)
395 static int revwalk_next_reverse(git_commit_list_node
**object_out
, git_revwalk
*walk
)
397 *object_out
= git_commit_list_pop(&walk
->iterator_reverse
);
398 return *object_out
? 0 : GIT_ITEROVER
;
402 static int interesting(git_pqueue
*list
)
406 for (i
= 0; i
< git_pqueue_size(list
); i
++) {
407 git_commit_list_node
*commit
= git_pqueue_get(list
, i
);
408 if (!commit
->uninteresting
)
415 static int contains(git_pqueue
*list
, git_commit_list_node
*node
)
419 for (i
= 0; i
< git_pqueue_size(list
); i
++) {
420 git_commit_list_node
*commit
= git_pqueue_get(list
, i
);
428 static int premark_uninteresting(git_revwalk
*walk
)
433 git_commit_list
*list
;
434 git_commit_list_node
*commit
, *parent
;
436 if ((error
= git_pqueue_init(&q
, 0, 8, git_commit_list_time_cmp
)) < 0)
439 for (list
= walk
->user_input
; list
; list
= list
->next
) {
440 if ((error
= git_commit_list_parse(walk
, list
->item
)) < 0)
443 if ((error
= git_pqueue_insert(&q
, list
->item
)) < 0)
447 while (interesting(&q
)) {
448 commit
= git_pqueue_pop(&q
);
450 for (i
= 0; i
< commit
->out_degree
; i
++) {
451 parent
= commit
->parents
[i
];
453 if ((error
= git_commit_list_parse(walk
, parent
)) < 0)
456 if (commit
->uninteresting
)
457 parent
->uninteresting
= 1;
459 if (contains(&q
, parent
))
462 if ((error
= git_pqueue_insert(&q
, parent
)) < 0)
472 static int prepare_walk(git_revwalk
*walk
)
475 git_commit_list
*list
;
476 git_commit_list_node
*next
;
478 /* If there were no pushes, we know that the walk is already over */
479 if (!walk
->did_push
) {
484 if (walk
->did_hide
&& (error
= premark_uninteresting(walk
)) < 0)
487 for (list
= walk
->user_input
; list
; list
= list
->next
) {
488 if (process_commit(walk
, list
->item
, list
->item
->uninteresting
) < 0)
493 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
496 while ((error
= walk
->get_next(&next
, walk
)) == 0) {
497 for (i
= 0; i
< next
->out_degree
; ++i
) {
498 git_commit_list_node
*parent
= next
->parents
[i
];
502 if (git_commit_list_insert(next
, &walk
->iterator_topo
) == NULL
)
506 if (error
!= GIT_ITEROVER
)
509 walk
->get_next
= &revwalk_next_toposort
;
512 if (walk
->sorting
& GIT_SORT_REVERSE
) {
514 while ((error
= walk
->get_next(&next
, walk
)) == 0)
515 if (git_commit_list_insert(next
, &walk
->iterator_reverse
) == NULL
)
518 if (error
!= GIT_ITEROVER
)
521 walk
->get_next
= &revwalk_next_reverse
;
529 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
531 git_revwalk
*walk
= git__calloc(1, sizeof(git_revwalk
));
532 GITERR_CHECK_ALLOC(walk
);
534 walk
->commits
= git_oidmap_alloc();
535 GITERR_CHECK_ALLOC(walk
->commits
);
537 if (git_pqueue_init(&walk
->iterator_time
, 0, 8, git_commit_list_time_cmp
) < 0)
540 git_pool_init(&walk
->commit_pool
, COMMIT_ALLOC
);
541 walk
->get_next
= &revwalk_next_unsorted
;
542 walk
->enqueue
= &revwalk_enqueue_unsorted
;
546 if (git_repository_odb(&walk
->odb
, repo
) < 0) {
547 git_revwalk_free(walk
);
555 void git_revwalk_free(git_revwalk
*walk
)
560 git_revwalk_reset(walk
);
561 git_odb_free(walk
->odb
);
563 git_oidmap_free(walk
->commits
);
564 git_pool_clear(&walk
->commit_pool
);
565 git_pqueue_free(&walk
->iterator_time
);
569 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
575 void git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
580 git_revwalk_reset(walk
);
582 walk
->sorting
= sort_mode
;
584 if (walk
->sorting
& GIT_SORT_TIME
) {
585 walk
->get_next
= &revwalk_next_timesort
;
586 walk
->enqueue
= &revwalk_enqueue_timesort
;
588 walk
->get_next
= &revwalk_next_unsorted
;
589 walk
->enqueue
= &revwalk_enqueue_unsorted
;
593 void git_revwalk_simplify_first_parent(git_revwalk
*walk
)
595 walk
->first_parent
= 1;
598 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
601 git_commit_list_node
*next
;
605 if (!walk
->walking
) {
606 if ((error
= prepare_walk(walk
)) < 0)
610 error
= walk
->get_next(&next
, walk
);
612 if (error
== GIT_ITEROVER
) {
613 git_revwalk_reset(walk
);
619 git_oid_cpy(oid
, &next
->oid
);
624 void git_revwalk_reset(git_revwalk
*walk
)
626 git_commit_list_node
*commit
;
630 kh_foreach_value(walk
->commits
, commit
, {
632 commit
->in_degree
= 0;
633 commit
->topo_delay
= 0;
634 commit
->uninteresting
= 0;
638 git_pqueue_clear(&walk
->iterator_time
);
639 git_commit_list_free(&walk
->iterator_topo
);
640 git_commit_list_free(&walk
->iterator_rand
);
641 git_commit_list_free(&walk
->iterator_reverse
);
642 git_commit_list_free(&walk
->user_input
);
643 walk
->first_parent
= 0;
645 walk
->did_push
= walk
->did_hide
= 0;
648 int git_revwalk_add_hide_cb(
650 git_revwalk_hide_cb hide_cb
,
656 git_revwalk_reset(walk
);
659 /* There is already a callback added */
660 giterr_set(GITERR_INVALID
, "There is already a callback added to hide commits in revision walker.");
664 walk
->hide_cb
= hide_cb
;
665 walk
->hide_cb_payload
= payload
;