]>
git.proxmox.com Git - libgit2.git/blob - src/revwalk.c
2 * Copyright (C) 2009-2012 the libgit2 contributors
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.
11 #include "hashtable.h"
14 #include "git2/revwalk.h"
16 typedef struct commit_object
{
24 unsigned short in_degree
;
25 unsigned short out_degree
;
27 struct commit_object
**parents
;
30 typedef struct commit_list
{
32 struct commit_list
*next
;
39 git_hashtable
*commits
;
41 commit_list
*iterator_topo
;
42 commit_list
*iterator_rand
;
43 commit_list
*iterator_reverse
;
44 git_pqueue iterator_time
;
46 int (*get_next
)(commit_object
**, git_revwalk
*);
47 int (*enqueue
)(git_revwalk
*, commit_object
*);
49 git_vector memory_alloc
;
56 static commit_list
*commit_list_insert(commit_object
*item
, commit_list
**list_p
)
58 commit_list
*new_list
= git__malloc(sizeof(commit_list
));
59 new_list
->item
= item
;
60 new_list
->next
= *list_p
;
65 static void commit_list_free(commit_list
**list_p
)
67 commit_list
*list
= *list_p
;
70 commit_list
*temp
= list
;
78 static commit_object
*commit_list_pop(commit_list
**stack
)
80 commit_list
*top
= *stack
;
81 commit_object
*item
= top
? top
->item
: NULL
;
90 static int commit_time_cmp(void *a
, void *b
)
92 commit_object
*commit_a
= (commit_object
*)a
;
93 commit_object
*commit_b
= (commit_object
*)b
;
95 return (commit_a
->time
< commit_b
->time
);
98 static uint32_t object_table_hash(const void *key
, int hash_id
)
101 const git_oid
*id
= key
;
103 memcpy(&r
, id
->id
+ (hash_id
* sizeof(uint32_t)), sizeof(r
));
107 #define COMMITS_PER_CHUNK 128
108 #define CHUNK_STEP 64
109 #define PARENTS_PER_COMMIT ((CHUNK_STEP - sizeof(commit_object)) / sizeof(commit_object *))
111 static int alloc_chunk(git_revwalk
*walk
)
115 chunk
= git__calloc(COMMITS_PER_CHUNK
, CHUNK_STEP
);
119 walk
->chunk_size
= 0;
120 return git_vector_insert(&walk
->memory_alloc
, chunk
);
123 static commit_object
*alloc_commit(git_revwalk
*walk
)
125 unsigned char *chunk
;
127 if (walk
->chunk_size
== COMMITS_PER_CHUNK
)
130 chunk
= git_vector_get(&walk
->memory_alloc
, walk
->memory_alloc
.length
- 1);
131 chunk
+= (walk
->chunk_size
* CHUNK_STEP
);
134 return (commit_object
*)chunk
;
137 static commit_object
**alloc_parents(commit_object
*commit
, size_t n_parents
)
139 if (n_parents
<= PARENTS_PER_COMMIT
)
140 return (commit_object
**)((unsigned char *)commit
+ sizeof(commit_object
));
142 return git__malloc(n_parents
* sizeof(commit_object
*));
146 static commit_object
*commit_lookup(git_revwalk
*walk
, const git_oid
*oid
)
148 commit_object
*commit
;
150 if ((commit
= git_hashtable_lookup(walk
->commits
, oid
)) != NULL
)
153 commit
= alloc_commit(walk
);
157 git_oid_cpy(&commit
->oid
, oid
);
159 if (git_hashtable_insert(walk
->commits
, &commit
->oid
, commit
) < GIT_SUCCESS
) {
167 static int commit_quick_parse(git_revwalk
*walk
, commit_object
*commit
, git_rawobj
*raw
)
169 const int parent_len
= strlen("parent ") + GIT_OID_HEXSZ
+ 1;
171 unsigned char *buffer
= raw
->data
;
172 unsigned char *buffer_end
= buffer
+ raw
->len
;
173 unsigned char *parents_start
;
178 buffer
+= strlen("tree ") + GIT_OID_HEXSZ
+ 1;
180 parents_start
= buffer
;
181 while (buffer
+ parent_len
< buffer_end
&& memcmp(buffer
, "parent ", strlen("parent ")) == 0) {
183 buffer
+= parent_len
;
186 commit
->parents
= alloc_parents(commit
, parents
);
187 if (commit
->parents
== NULL
)
190 buffer
= parents_start
;
191 for (i
= 0; i
< parents
; ++i
) {
194 if (git_oid_fromstr(&oid
, (char *)buffer
+ strlen("parent ")) < GIT_SUCCESS
)
195 return git__throw(GIT_EOBJCORRUPTED
, "Failed to parse commit. Parent object is corrupted");
197 commit
->parents
[i
] = commit_lookup(walk
, &oid
);
198 if (commit
->parents
[i
] == NULL
)
201 buffer
+= parent_len
;
204 commit
->out_degree
= (unsigned short)parents
;
206 if ((buffer
= memchr(buffer
, '\n', buffer_end
- buffer
)) == NULL
)
207 return git__throw(GIT_EOBJCORRUPTED
, "Failed to parse commit. Object is corrupted");
209 buffer
= memchr(buffer
, '>', buffer_end
- buffer
);
211 return git__throw(GIT_EOBJCORRUPTED
, "Failed to parse commit. Can't find author");
213 if (git__strtol32(&commit_time
, (char *)buffer
+ 2, NULL
, 10) < GIT_SUCCESS
)
214 return git__throw(GIT_EOBJCORRUPTED
, "Failed to parse commit. Can't parse commit time");
216 commit
->time
= (time_t)commit_time
;
221 static int commit_parse(git_revwalk
*walk
, commit_object
*commit
)
229 if ((error
= git_odb_read(&obj
, walk
->odb
, &commit
->oid
)) < GIT_SUCCESS
)
230 return git__rethrow(error
, "Failed to parse commit. Can't read object");
232 if (obj
->raw
.type
!= GIT_OBJ_COMMIT
) {
233 git_odb_object_free(obj
);
234 return git__throw(GIT_EOBJTYPE
, "Failed to parse commit. Object is no commit object");
237 error
= commit_quick_parse(walk
, commit
, &obj
->raw
);
238 git_odb_object_free(obj
);
239 return error
== GIT_SUCCESS
? GIT_SUCCESS
: git__rethrow(error
, "Failed to parse commit");
242 static void mark_uninteresting(commit_object
*commit
)
247 commit
->uninteresting
= 1;
249 for (i
= 0; i
< commit
->out_degree
; ++i
)
250 if (!commit
->parents
[i
]->uninteresting
)
251 mark_uninteresting(commit
->parents
[i
]);
254 static int process_commit(git_revwalk
*walk
, commit_object
*commit
, int hide
)
259 mark_uninteresting(commit
);
266 if ((error
= commit_parse(walk
, commit
)) < GIT_SUCCESS
)
267 return git__rethrow(error
, "Failed to process commit");
269 return walk
->enqueue(walk
, commit
);
272 static int process_commit_parents(git_revwalk
*walk
, commit_object
*commit
)
275 int error
= GIT_SUCCESS
;
277 for (i
= 0; i
< commit
->out_degree
&& error
== GIT_SUCCESS
; ++i
) {
278 error
= process_commit(walk
, commit
->parents
[i
], commit
->uninteresting
);
281 return error
== GIT_SUCCESS
? GIT_SUCCESS
: git__rethrow(error
, "Failed to process commit parents");
284 static int push_commit(git_revwalk
*walk
, const git_oid
*oid
, int uninteresting
)
286 commit_object
*commit
;
288 commit
= commit_lookup(walk
, oid
);
290 return git__throw(GIT_ENOTFOUND
, "Failed to push commit. Object not found");
292 return process_commit(walk
, commit
, uninteresting
);
295 int git_revwalk_push(git_revwalk
*walk
, const git_oid
*oid
)
298 return push_commit(walk
, oid
, 0);
301 int git_revwalk_hide(git_revwalk
*walk
, const git_oid
*oid
)
304 return push_commit(walk
, oid
, 1);
307 static int revwalk_enqueue_timesort(git_revwalk
*walk
, commit_object
*commit
)
309 return git_pqueue_insert(&walk
->iterator_time
, commit
);
312 static int revwalk_enqueue_unsorted(git_revwalk
*walk
, commit_object
*commit
)
314 return commit_list_insert(commit
, &walk
->iterator_rand
) ? GIT_SUCCESS
: GIT_ENOMEM
;
317 static int revwalk_next_timesort(commit_object
**object_out
, git_revwalk
*walk
)
322 while ((next
= git_pqueue_pop(&walk
->iterator_time
)) != NULL
) {
323 if ((error
= process_commit_parents(walk
, next
)) < GIT_SUCCESS
)
324 return git__rethrow(error
, "Failed to load next revision");
326 if (!next
->uninteresting
) {
332 return git__throw(GIT_EREVWALKOVER
, "Failed to load next revision");
335 static int revwalk_next_unsorted(commit_object
**object_out
, git_revwalk
*walk
)
340 while ((next
= commit_list_pop(&walk
->iterator_rand
)) != NULL
) {
341 if ((error
= process_commit_parents(walk
, next
)) < GIT_SUCCESS
)
342 return git__rethrow(error
, "Failed to load next revision");
344 if (!next
->uninteresting
) {
350 return git__throw(GIT_EREVWALKOVER
, "Failed to load next revision");
353 static int revwalk_next_toposort(commit_object
**object_out
, git_revwalk
*walk
)
359 next
= commit_list_pop(&walk
->iterator_topo
);
361 return git__throw(GIT_EREVWALKOVER
, "Failed to load next revision");
363 if (next
->in_degree
> 0) {
364 next
->topo_delay
= 1;
368 for (i
= 0; i
< next
->out_degree
; ++i
) {
369 commit_object
*parent
= next
->parents
[i
];
371 if (--parent
->in_degree
== 0 && parent
->topo_delay
) {
372 parent
->topo_delay
= 0;
373 commit_list_insert(parent
, &walk
->iterator_topo
);
382 static int revwalk_next_reverse(commit_object
**object_out
, git_revwalk
*walk
)
384 *object_out
= commit_list_pop(&walk
->iterator_reverse
);
385 return *object_out
? GIT_SUCCESS
: GIT_EREVWALKOVER
;
389 static int prepare_walk(git_revwalk
*walk
)
394 if (walk
->sorting
& GIT_SORT_TOPOLOGICAL
) {
397 while ((error
= walk
->get_next(&next
, walk
)) == GIT_SUCCESS
) {
398 for (i
= 0; i
< next
->out_degree
; ++i
) {
399 commit_object
*parent
= next
->parents
[i
];
403 commit_list_insert(next
, &walk
->iterator_topo
);
406 if (error
!= GIT_EREVWALKOVER
)
407 return git__rethrow(error
, "Failed to prepare revision walk");
409 walk
->get_next
= &revwalk_next_toposort
;
412 if (walk
->sorting
& GIT_SORT_REVERSE
) {
414 while ((error
= walk
->get_next(&next
, walk
)) == GIT_SUCCESS
)
415 commit_list_insert(next
, &walk
->iterator_reverse
);
417 if (error
!= GIT_EREVWALKOVER
)
418 return git__rethrow(error
, "Failed to prepare revision walk");
420 walk
->get_next
= &revwalk_next_reverse
;
431 int git_revwalk_new(git_revwalk
**revwalk_out
, git_repository
*repo
)
436 walk
= git__malloc(sizeof(git_revwalk
));
440 memset(walk
, 0x0, sizeof(git_revwalk
));
442 walk
->commits
= git_hashtable_alloc(64,
444 (git_hash_keyeq_ptr
)git_oid_cmp
);
446 if (walk
->commits
== NULL
) {
451 git_pqueue_init(&walk
->iterator_time
, 8, commit_time_cmp
);
452 git_vector_init(&walk
->memory_alloc
, 8, NULL
);
455 walk
->get_next
= &revwalk_next_unsorted
;
456 walk
->enqueue
= &revwalk_enqueue_unsorted
;
460 error
= git_repository_odb(&walk
->odb
, repo
);
461 if (error
< GIT_SUCCESS
) {
462 git_revwalk_free(walk
);
470 void git_revwalk_free(git_revwalk
*walk
)
473 const void *GIT_UNUSED(_unused
);
474 commit_object
*commit
;
479 git_revwalk_reset(walk
);
480 git_odb_free(walk
->odb
);
482 /* if the parent has more than PARENTS_PER_COMMIT parents,
483 * we had to allocate a separate array for those parents.
484 * make sure it's being free'd */
485 GIT_HASHTABLE_FOREACH(walk
->commits
, _unused
, commit
, {
486 if (commit
->out_degree
> PARENTS_PER_COMMIT
)
487 git__free(commit
->parents
);
490 git_hashtable_free(walk
->commits
);
491 git_pqueue_free(&walk
->iterator_time
);
493 for (i
= 0; i
< walk
->memory_alloc
.length
; ++i
)
494 git__free(git_vector_get(&walk
->memory_alloc
, i
));
496 git_vector_free(&walk
->memory_alloc
);
500 git_repository
*git_revwalk_repository(git_revwalk
*walk
)
506 void git_revwalk_sorting(git_revwalk
*walk
, unsigned int sort_mode
)
511 git_revwalk_reset(walk
);
513 walk
->sorting
= sort_mode
;
515 if (walk
->sorting
& GIT_SORT_TIME
) {
516 walk
->get_next
= &revwalk_next_timesort
;
517 walk
->enqueue
= &revwalk_enqueue_timesort
;
519 walk
->get_next
= &revwalk_next_unsorted
;
520 walk
->enqueue
= &revwalk_enqueue_unsorted
;
524 int git_revwalk_next(git_oid
*oid
, git_revwalk
*walk
)
531 if (!walk
->walking
) {
532 if ((error
= prepare_walk(walk
)) < GIT_SUCCESS
)
533 return git__rethrow(error
, "Failed to load next revision");
536 error
= walk
->get_next(&next
, walk
);
538 if (error
== GIT_EREVWALKOVER
) {
539 git_revwalk_reset(walk
);
540 return GIT_EREVWALKOVER
;
543 if (error
< GIT_SUCCESS
)
544 return git__rethrow(error
, "Failed to load next revision");
546 git_oid_cpy(oid
, &next
->oid
);
550 void git_revwalk_reset(git_revwalk
*walk
)
552 const void *GIT_UNUSED(_unused
);
553 commit_object
*commit
;
557 GIT_HASHTABLE_FOREACH(walk
->commits
, _unused
, commit
,
559 commit
->in_degree
= 0;
560 commit
->topo_delay
= 0;
561 commit
->uninteresting
= 0;
564 git_pqueue_clear(&walk
->iterator_time
);
565 commit_list_free(&walk
->iterator_topo
);
566 commit_list_free(&walk
->iterator_rand
);
567 commit_list_free(&walk
->iterator_reverse
);