]> git.proxmox.com Git - libgit2.git/blame - src/revwalk.c
I broke your bindings
[libgit2.git] / src / revwalk.c
CommitLineData
06160502 1/*
50298f44
SP
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
06160502 5 *
50298f44
SP
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
06160502 14 *
50298f44
SP
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
06160502 19 *
50298f44
SP
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
06160502
SP
24 */
25
64a47c01 26#include "common.h"
08d5d000 27#include "commit.h"
72a3fe42 28#include "odb.h"
3315782c 29#include "hashtable.h"
71db842f 30#include "pqueue.h"
06160502 31
b5c5f0f8
VM
32#include "git2/revwalk.h"
33
71db842f
VM
34typedef struct commit_object {
35 git_oid oid;
36 uint32_t time;
37 unsigned int seen:1,
38 uninteresting:1,
39 topo_delay:1,
40 parsed:1;
41
42 unsigned short in_degree;
43 unsigned short out_degree;
44
45 struct commit_object **parents;
46} commit_object;
47
48typedef struct commit_list {
49 commit_object *item;
50 struct commit_list *next;
51} commit_list;
52
53struct git_revwalk {
54 git_repository *repo;
55
56 git_hashtable *commits;
71db842f
VM
57
58 commit_list *iterator_topo;
59 commit_list *iterator_rand;
60 commit_list *iterator_reverse;
61 git_pqueue iterator_time;
62
63 int (*get_next)(commit_object **, git_revwalk *);
64 int (*enqueue)(git_revwalk *, commit_object *);
65
66 git_vector memory_alloc;
67 size_t chunk_size;
68
69 unsigned walking:1;
70 unsigned int sorting;
71};
72
73commit_list *commit_list_insert(commit_object *item, commit_list **list_p)
74{
75 commit_list *new_list = git__malloc(sizeof(commit_list));
76 new_list->item = item;
77 new_list->next = *list_p;
78 *list_p = new_list;
79 return new_list;
80}
81
36b31329 82void commit_list_free(commit_list **list_p)
71db842f 83{
36b31329
VM
84 commit_list *list = *list_p;
85
71db842f
VM
86 while (list) {
87 commit_list *temp = list;
88 list = temp->next;
89 free(temp);
90 }
36b31329
VM
91
92 *list_p = NULL;
71db842f
VM
93}
94
95commit_object *commit_list_pop(commit_list **stack)
96{
97 commit_list *top = *stack;
98 commit_object *item = top ? top->item : NULL;
99
100 if (top) {
101 *stack = top->next;
102 free(top);
103 }
104 return item;
105}
106
107static int commit_time_cmp(void *a, void *b)
108{
109 commit_object *commit_a = (commit_object *)a;
110 commit_object *commit_b = (commit_object *)b;
111
112 return (commit_a->time < commit_b->time);
113}
114
115static uint32_t object_table_hash(const void *key, int hash_id)
3315782c
VM
116{
117 uint32_t r;
71db842f 118 git_oid *id;
5e15176d 119
71db842f
VM
120 id = (git_oid *)key;
121 memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r));
3315782c
VM
122 return r;
123}
124
71db842f
VM
125#define COMMITS_PER_CHUNK 128
126#define CHUNK_STEP 64
127#define PARENTS_PER_COMMIT ((CHUNK_STEP - sizeof(commit_object)) / sizeof(commit_object *))
128
129static int alloc_chunk(git_revwalk *walk)
06160502 130{
71db842f
VM
131 void *chunk;
132
133 chunk = git__calloc(COMMITS_PER_CHUNK, CHUNK_STEP);
134 if (chunk == NULL)
135 return GIT_ENOMEM;
136
137 walk->chunk_size = 0;
138 return git_vector_insert(&walk->memory_alloc, chunk);
139}
140
141static commit_object *alloc_commit(git_revwalk *walk)
142{
143 unsigned char *chunk;
144
145 if (walk->chunk_size == COMMITS_PER_CHUNK)
146 alloc_chunk(walk);
147
148 chunk = git_vector_get(&walk->memory_alloc, walk->memory_alloc.length - 1);
149 chunk += (walk->chunk_size * CHUNK_STEP);
150 walk->chunk_size++;
151
152 return (commit_object *)chunk;
153}
154
155static commit_object **alloc_parents(commit_object *commit, size_t n_parents)
156{
157 if (n_parents <= PARENTS_PER_COMMIT)
158 return (commit_object **)((unsigned char *)commit + sizeof(commit_object));
159
160 return git__malloc(n_parents * sizeof(commit_object *));
3315782c
VM
161}
162
40721f6b 163
71db842f 164static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
3315782c 165{
71db842f 166 commit_object *commit;
58b0cbea 167
71db842f 168 if ((commit = git_hashtable_lookup(walk->commits, oid)) != NULL)
3315782c 169 return commit;
40721f6b 170
71db842f 171 commit = alloc_commit(walk);
3315782c
VM
172 if (commit == NULL)
173 return NULL;
5e15176d 174
71db842f 175 git_oid_cpy(&commit->oid, oid);
3315782c 176
71db842f
VM
177 if (git_hashtable_insert(walk->commits, &commit->oid, commit) < GIT_SUCCESS) {
178 free(commit);
179 return NULL;
180 }
3315782c
VM
181
182 return commit;
06160502 183}
08d5d000 184
71db842f 185static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw)
e5d1faef 186{
71db842f 187 const int parent_len = STRLEN("parent ") + GIT_OID_HEXSZ + 1;
3315782c 188
71db842f
VM
189 unsigned char *buffer = raw->data;
190 unsigned char *buffer_end = buffer + raw->len;
191 unsigned char *parents_start;
3315782c 192
71db842f 193 int i, parents = 0;
3315782c 194
71db842f 195 buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1;
e5d1faef 196
71db842f
VM
197 parents_start = buffer;
198 while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", STRLEN("parent ")) == 0) {
199 parents++;
200 buffer += parent_len;
201 }
3315782c 202
71db842f
VM
203 commit->parents = alloc_parents(commit, parents);
204 if (commit->parents == NULL)
205 return GIT_ENOMEM;
3315782c 206
71db842f
VM
207 buffer = parents_start;
208 for (i = 0; i < parents; ++i) {
209 git_oid oid;
3315782c 210
71db842f
VM
211 if (git_oid_mkstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS)
212 return GIT_EOBJCORRUPTED;
eec95235 213
71db842f
VM
214 commit->parents[i] = commit_lookup(walk, &oid);
215 if (commit->parents[i] == NULL)
216 return GIT_ENOMEM;
3315782c 217
71db842f
VM
218 buffer += parent_len;
219 }
3315782c 220
71db842f 221 commit->out_degree = (unsigned short)parents;
3315782c 222
71db842f
VM
223 if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
224 return GIT_EOBJCORRUPTED;
3315782c 225
71db842f
VM
226 buffer = memchr(buffer, '>', buffer_end - buffer);
227 if (buffer == NULL)
228 return GIT_EOBJCORRUPTED;
3315782c 229
71db842f
VM
230 commit->time = strtol((char *)buffer + 2, NULL, 10);
231 if (commit->time == 0)
232 return GIT_EOBJCORRUPTED;
e5d1faef 233
71db842f
VM
234 commit->parsed = 1;
235 return GIT_SUCCESS;
3315782c 236}
8add0153 237
71db842f 238static int commit_parse(git_revwalk *walk, commit_object *commit)
3315782c 239{
72a3fe42 240 git_odb_object *obj;
71db842f 241 int error;
3315782c 242
71db842f
VM
243 if (commit->parsed)
244 return GIT_SUCCESS;
08d5d000 245
72a3fe42 246 if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS)
71db842f 247 return error;
52f2390b 248
72a3fe42
VM
249 if (obj->raw.type != GIT_OBJ_COMMIT) {
250 git_odb_object_close(obj);
71db842f 251 return GIT_EOBJTYPE;
9b3577ed 252 }
71db842f 253
72a3fe42
VM
254 error = commit_quick_parse(walk, commit, &obj->raw);
255 git_odb_object_close(obj);
71db842f 256 return error;
3315782c
VM
257}
258
71db842f 259static void mark_uninteresting(commit_object *commit)
3315782c 260{
71db842f
VM
261 unsigned short i;
262 assert(commit);
1f080e2d 263
71db842f 264 commit->uninteresting = 1;
08d5d000 265
71db842f
VM
266 for (i = 0; i < commit->out_degree; ++i)
267 if (!commit->parents[i]->uninteresting)
268 mark_uninteresting(commit->parents[i]);
3315782c 269}
1a895dd7 270
71db842f 271static int process_commit(git_revwalk *walk, commit_object *commit)
3315782c 272{
71db842f 273 int error;
3315782c 274
71db842f
VM
275 if (commit->seen)
276 return GIT_SUCCESS;
3315782c 277
71db842f 278 commit->seen = 1;
1a895dd7 279
71db842f
VM
280 if ((error = commit_parse(walk, commit)) < GIT_SUCCESS)
281 return error;
e5d1faef 282
71db842f
VM
283 if (commit->uninteresting)
284 mark_uninteresting(commit);
1f080e2d 285
71db842f
VM
286 return walk->enqueue(walk, commit);
287}
3315782c 288
71db842f
VM
289static int process_commit_parents(git_revwalk *walk, commit_object *commit)
290{
291 unsigned short i;
292 int error = GIT_SUCCESS;
c836c332 293
71db842f
VM
294 for (i = 0; i < commit->out_degree && error == GIT_SUCCESS; ++i) {
295 error = process_commit(walk, commit->parents[i]);
3315782c
VM
296 }
297
71db842f 298 return error;
08d5d000
VM
299}
300
71db842f 301static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting)
08d5d000 302{
71db842f 303 commit_object *commit;
1a895dd7 304
71db842f
VM
305 commit = commit_lookup(walk, oid);
306 if (commit == NULL)
307 return GIT_ENOTFOUND;
1f080e2d 308
36aaf1ff 309 commit->uninteresting = uninteresting;
1a895dd7 310
36aaf1ff 311 return process_commit(walk, commit);
3315782c
VM
312}
313
71db842f
VM
314int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
315{
316 assert(walk && oid);
317 return push_commit(walk, oid, 0);
318}
3315782c 319
71db842f
VM
320int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
321{
322 assert(walk && oid);
323 return push_commit(walk, oid, 1);
324}
3315782c 325
71db842f 326static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit)
3315782c 327{
71db842f
VM
328 return git_pqueue_insert(&walk->iterator_time, commit);
329}
3315782c 330
71db842f
VM
331static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit)
332{
333 return commit_list_insert(commit, &walk->iterator_rand) ? GIT_SUCCESS : GIT_ENOMEM;
334}
3315782c 335
71db842f
VM
336static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)
337{
338 int error;
339 commit_object *next;
5e15176d 340
71db842f
VM
341 while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
342 if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
343 return error;
6bb7aa13 344
71db842f
VM
345 if (!next->uninteresting) {
346 *object_out = next;
347 return GIT_SUCCESS;
348 }
9b3577ed 349 }
08d5d000 350
71db842f 351 return GIT_EREVWALKOVER;
3315782c
VM
352}
353
71db842f 354static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)
3315782c 355{
71db842f
VM
356 int error;
357 commit_object *next;
3315782c 358
71db842f
VM
359 while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) {
360 if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
361 return error;
6bb7aa13 362
71db842f
VM
363 if (!next->uninteresting) {
364 *object_out = next;
365 return GIT_SUCCESS;
366 }
3315782c
VM
367 }
368
71db842f 369 return GIT_EREVWALKOVER;
a7c182c5 370}
1a895dd7 371
71db842f 372static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
a7c182c5 373{
71db842f
VM
374 commit_object *next;
375 unsigned short i;
1a895dd7 376
71db842f
VM
377 for (;;) {
378 next = commit_list_pop(&walk->iterator_topo);
379 if (next == NULL)
380 return GIT_EREVWALKOVER;
5e15176d 381
71db842f
VM
382 if (next->in_degree > 0) {
383 next->topo_delay = 1;
384 continue;
385 }
655d381a 386
71db842f
VM
387 for (i = 0; i < next->out_degree; ++i) {
388 commit_object *parent = next->parents[i];
5e15176d 389
71db842f
VM
390 if (--parent->in_degree == 0 && parent->topo_delay) {
391 parent->topo_delay = 0;
392 commit_list_insert(parent, &walk->iterator_topo);
393 }
394 }
5e15176d 395
71db842f
VM
396 *object_out = next;
397 return GIT_SUCCESS;
398 }
5e15176d
VM
399}
400
71db842f 401static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk)
5e15176d 402{
71db842f
VM
403 *object_out = commit_list_pop(&walk->iterator_reverse);
404 return *object_out ? GIT_SUCCESS : GIT_EREVWALKOVER;
405}
5e15176d 406
3315782c 407
71db842f
VM
408static int prepare_walk(git_revwalk *walk)
409{
71db842f 410 int error;
36aaf1ff 411 commit_object *next;
08d5d000 412
71db842f 413 if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
71db842f 414 unsigned short i;
08d5d000 415
71db842f
VM
416 while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) {
417 for (i = 0; i < next->out_degree; ++i) {
418 commit_object *parent = next->parents[i];
419 parent->in_degree++;
420 }
3315782c 421
71db842f
VM
422 commit_list_insert(next, &walk->iterator_topo);
423 }
3315782c 424
71db842f
VM
425 if (error != GIT_EREVWALKOVER)
426 return error;
3315782c 427
71db842f
VM
428 walk->get_next = &revwalk_next_toposort;
429 }
3315782c 430
71db842f 431 if (walk->sorting & GIT_SORT_REVERSE) {
3315782c 432
71db842f
VM
433 while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS)
434 commit_list_insert(next, &walk->iterator_reverse);
3315782c 435
71db842f
VM
436 if (error != GIT_EREVWALKOVER)
437 return error;
9bdb7594 438
71db842f
VM
439 walk->get_next = &revwalk_next_reverse;
440 }
40721f6b 441
71db842f
VM
442 walk->walking = 1;
443 return GIT_SUCCESS;
444}
3315782c 445
3315782c 446
36aaf1ff
VM
447
448
449
450int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
451{
452 git_revwalk *walk;
453
454 walk = git__malloc(sizeof(git_revwalk));
455 if (walk == NULL)
456 return GIT_ENOMEM;
457
458 memset(walk, 0x0, sizeof(git_revwalk));
459
460 walk->commits = git_hashtable_alloc(64,
461 object_table_hash,
462 (git_hash_keyeq_ptr)git_oid_cmp);
463
464 if (walk->commits == NULL) {
465 free(walk);
466 return GIT_ENOMEM;
467 }
468
469 git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp);
470 git_vector_init(&walk->memory_alloc, 8, NULL);
471 alloc_chunk(walk);
472
473 walk->get_next = &revwalk_next_unsorted;
474 walk->enqueue = &revwalk_enqueue_unsorted;
475
476 walk->repo = repo;
477
478 *revwalk_out = walk;
479 return GIT_SUCCESS;
480}
481
482void git_revwalk_free(git_revwalk *walk)
483{
484 unsigned int i;
485
486 if (walk == NULL)
487 return;
488
489 git_revwalk_reset(walk);
490 git_hashtable_free(walk->commits);
491 git_pqueue_free(&walk->iterator_time);
492
493 for (i = 0; i < walk->memory_alloc.length; ++i)
494 free(git_vector_get(&walk->memory_alloc, i));
495
496 git_vector_free(&walk->memory_alloc);
497 free(walk);
498}
499
500git_repository *git_revwalk_repository(git_revwalk *walk)
501{
502 assert(walk);
503 return walk->repo;
504}
505
506void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
507{
508 assert(walk);
509
510 if (walk->walking)
511 git_revwalk_reset(walk);
512
513 walk->sorting = sort_mode;
514
515 if (walk->sorting & GIT_SORT_TIME) {
516 walk->get_next = &revwalk_next_timesort;
517 walk->enqueue = &revwalk_enqueue_timesort;
518 } else {
519 walk->get_next = &revwalk_next_unsorted;
520 walk->enqueue = &revwalk_enqueue_unsorted;
521 }
522}
523
71db842f
VM
524int git_revwalk_next(git_oid *oid, git_revwalk *walk)
525{
526 int error;
527 commit_object *next;
3315782c 528
71db842f 529 assert(walk && oid);
3315782c 530
71db842f
VM
531 if (!walk->walking) {
532 if ((error = prepare_walk(walk)) < GIT_SUCCESS)
533 return error;
534 }
3315782c 535
71db842f 536 error = walk->get_next(&next, walk);
36aaf1ff
VM
537 if (error < GIT_SUCCESS) {
538 if (error == GIT_EREVWALKOVER)
539 git_revwalk_reset(walk);
71db842f 540 return error;
36aaf1ff 541 }
3315782c 542
71db842f
VM
543 git_oid_cpy(oid, &next->oid);
544 return GIT_SUCCESS;
3315782c
VM
545}
546
71db842f 547void git_revwalk_reset(git_revwalk *walk)
3315782c 548{
71db842f
VM
549 const void *_unused;
550 commit_object *commit;
3315782c 551
71db842f 552 assert(walk);
9bdb7594 553
71db842f
VM
554 GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit,
555 commit->seen = 0;
556 commit->in_degree = 0;
557 commit->topo_delay = 0;
558 );
9bdb7594 559
36aaf1ff 560 git_pqueue_clear(&walk->iterator_time);
36b31329
VM
561 commit_list_free(&walk->iterator_topo);
562 commit_list_free(&walk->iterator_rand);
563 commit_list_free(&walk->iterator_reverse);
71db842f 564 walk->walking = 0;
08d5d000 565}
36b7cdb6 566