]> git.proxmox.com Git - libgit2.git/blame - src/revwalk.c
cmake: Do not pass `/Tc` to MSVC
[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;
c6e65aca 194 long commit_time;
3315782c 195
71db842f 196 buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1;
e5d1faef 197
71db842f
VM
198 parents_start = buffer;
199 while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", STRLEN("parent ")) == 0) {
200 parents++;
201 buffer += parent_len;
202 }
3315782c 203
71db842f
VM
204 commit->parents = alloc_parents(commit, parents);
205 if (commit->parents == NULL)
206 return GIT_ENOMEM;
3315782c 207
71db842f
VM
208 buffer = parents_start;
209 for (i = 0; i < parents; ++i) {
210 git_oid oid;
3315782c 211
fa48608e 212 if (git_oid_fromstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS)
b51c9269 213 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted");
eec95235 214
71db842f
VM
215 commit->parents[i] = commit_lookup(walk, &oid);
216 if (commit->parents[i] == NULL)
217 return GIT_ENOMEM;
3315782c 218
71db842f
VM
219 buffer += parent_len;
220 }
3315782c 221
71db842f 222 commit->out_degree = (unsigned short)parents;
3315782c 223
71db842f 224 if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
b51c9269 225 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted");
3315782c 226
71db842f
VM
227 buffer = memchr(buffer, '>', buffer_end - buffer);
228 if (buffer == NULL)
b51c9269 229 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author");
3315782c 230
c6e65aca 231 if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS)
b51c9269 232 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time");
e5d1faef 233
c6e65aca 234 commit->time = (time_t)commit_time;
71db842f
VM
235 commit->parsed = 1;
236 return GIT_SUCCESS;
3315782c 237}
8add0153 238
71db842f 239static int commit_parse(git_revwalk *walk, commit_object *commit)
3315782c 240{
72a3fe42 241 git_odb_object *obj;
71db842f 242 int error;
3315782c 243
71db842f
VM
244 if (commit->parsed)
245 return GIT_SUCCESS;
08d5d000 246
72a3fe42 247 if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS)
b51c9269 248 return git__rethrow(error, "Failed to parse commit. Can't read object");
52f2390b 249
72a3fe42
VM
250 if (obj->raw.type != GIT_OBJ_COMMIT) {
251 git_odb_object_close(obj);
b51c9269 252 return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object");
9b3577ed 253 }
71db842f 254
72a3fe42
VM
255 error = commit_quick_parse(walk, commit, &obj->raw);
256 git_odb_object_close(obj);
c0cd9d50 257 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse commit");
3315782c
VM
258}
259
71db842f 260static void mark_uninteresting(commit_object *commit)
3315782c 261{
71db842f
VM
262 unsigned short i;
263 assert(commit);
1f080e2d 264
71db842f 265 commit->uninteresting = 1;
08d5d000 266
71db842f
VM
267 for (i = 0; i < commit->out_degree; ++i)
268 if (!commit->parents[i]->uninteresting)
269 mark_uninteresting(commit->parents[i]);
3315782c 270}
1a895dd7 271
71db842f 272static int process_commit(git_revwalk *walk, commit_object *commit)
3315782c 273{
71db842f 274 int error;
3315782c 275
71db842f
VM
276 if (commit->seen)
277 return GIT_SUCCESS;
3315782c 278
71db842f 279 commit->seen = 1;
1a895dd7 280
71db842f 281 if ((error = commit_parse(walk, commit)) < GIT_SUCCESS)
c0cd9d50 282 return git__rethrow(error, "Failed to process commit");
e5d1faef 283
71db842f
VM
284 if (commit->uninteresting)
285 mark_uninteresting(commit);
1f080e2d 286
71db842f
VM
287 return walk->enqueue(walk, commit);
288}
3315782c 289
71db842f
VM
290static int process_commit_parents(git_revwalk *walk, commit_object *commit)
291{
292 unsigned short i;
293 int error = GIT_SUCCESS;
c836c332 294
71db842f
VM
295 for (i = 0; i < commit->out_degree && error == GIT_SUCCESS; ++i) {
296 error = process_commit(walk, commit->parents[i]);
3315782c
VM
297 }
298
c0cd9d50 299 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to process commit parents");
08d5d000
VM
300}
301
71db842f 302static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting)
08d5d000 303{
71db842f 304 commit_object *commit;
1a895dd7 305
71db842f
VM
306 commit = commit_lookup(walk, oid);
307 if (commit == NULL)
b51c9269 308 return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found");
1f080e2d 309
36aaf1ff 310 commit->uninteresting = uninteresting;
1a895dd7 311
36aaf1ff 312 return process_commit(walk, commit);
3315782c
VM
313}
314
71db842f
VM
315int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
316{
317 assert(walk && oid);
318 return push_commit(walk, oid, 0);
319}
3315782c 320
71db842f
VM
321int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
322{
323 assert(walk && oid);
324 return push_commit(walk, oid, 1);
325}
3315782c 326
71db842f 327static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit)
3315782c 328{
71db842f
VM
329 return git_pqueue_insert(&walk->iterator_time, commit);
330}
3315782c 331
71db842f
VM
332static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit)
333{
334 return commit_list_insert(commit, &walk->iterator_rand) ? GIT_SUCCESS : GIT_ENOMEM;
335}
3315782c 336
71db842f
VM
337static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)
338{
339 int error;
340 commit_object *next;
5e15176d 341
71db842f
VM
342 while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
343 if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
c0cd9d50 344 return git__rethrow(error, "Failed to load next revision");
6bb7aa13 345
71db842f
VM
346 if (!next->uninteresting) {
347 *object_out = next;
348 return GIT_SUCCESS;
349 }
9b3577ed 350 }
08d5d000 351
c0cd9d50 352 return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
3315782c
VM
353}
354
71db842f 355static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)
3315782c 356{
71db842f
VM
357 int error;
358 commit_object *next;
3315782c 359
71db842f
VM
360 while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) {
361 if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
c0cd9d50 362 return git__rethrow(error, "Failed to load next revision");
6bb7aa13 363
71db842f
VM
364 if (!next->uninteresting) {
365 *object_out = next;
366 return GIT_SUCCESS;
367 }
3315782c
VM
368 }
369
c0cd9d50 370 return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
a7c182c5 371}
1a895dd7 372
71db842f 373static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
a7c182c5 374{
71db842f
VM
375 commit_object *next;
376 unsigned short i;
1a895dd7 377
71db842f
VM
378 for (;;) {
379 next = commit_list_pop(&walk->iterator_topo);
380 if (next == NULL)
c0cd9d50 381 return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
5e15176d 382
71db842f
VM
383 if (next->in_degree > 0) {
384 next->topo_delay = 1;
385 continue;
386 }
655d381a 387
71db842f
VM
388 for (i = 0; i < next->out_degree; ++i) {
389 commit_object *parent = next->parents[i];
5e15176d 390
71db842f
VM
391 if (--parent->in_degree == 0 && parent->topo_delay) {
392 parent->topo_delay = 0;
393 commit_list_insert(parent, &walk->iterator_topo);
394 }
395 }
5e15176d 396
71db842f
VM
397 *object_out = next;
398 return GIT_SUCCESS;
399 }
5e15176d
VM
400}
401
71db842f 402static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk)
5e15176d 403{
71db842f
VM
404 *object_out = commit_list_pop(&walk->iterator_reverse);
405 return *object_out ? GIT_SUCCESS : GIT_EREVWALKOVER;
406}
5e15176d 407
3315782c 408
71db842f
VM
409static int prepare_walk(git_revwalk *walk)
410{
71db842f 411 int error;
36aaf1ff 412 commit_object *next;
08d5d000 413
71db842f 414 if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
71db842f 415 unsigned short i;
08d5d000 416
71db842f
VM
417 while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) {
418 for (i = 0; i < next->out_degree; ++i) {
419 commit_object *parent = next->parents[i];
420 parent->in_degree++;
421 }
3315782c 422
71db842f
VM
423 commit_list_insert(next, &walk->iterator_topo);
424 }
3315782c 425
71db842f 426 if (error != GIT_EREVWALKOVER)
c0cd9d50 427 return git__rethrow(error, "Failed to prepare revision walk");
3315782c 428
71db842f
VM
429 walk->get_next = &revwalk_next_toposort;
430 }
3315782c 431
71db842f 432 if (walk->sorting & GIT_SORT_REVERSE) {
3315782c 433
71db842f
VM
434 while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS)
435 commit_list_insert(next, &walk->iterator_reverse);
3315782c 436
71db842f 437 if (error != GIT_EREVWALKOVER)
c0cd9d50 438 return git__rethrow(error, "Failed to prepare revision walk");
9bdb7594 439
71db842f
VM
440 walk->get_next = &revwalk_next_reverse;
441 }
40721f6b 442
71db842f
VM
443 walk->walking = 1;
444 return GIT_SUCCESS;
445}
3315782c 446
3315782c 447
36aaf1ff
VM
448
449
450
451int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
452{
453 git_revwalk *walk;
454
455 walk = git__malloc(sizeof(git_revwalk));
456 if (walk == NULL)
457 return GIT_ENOMEM;
458
459 memset(walk, 0x0, sizeof(git_revwalk));
460
461 walk->commits = git_hashtable_alloc(64,
462 object_table_hash,
463 (git_hash_keyeq_ptr)git_oid_cmp);
464
465 if (walk->commits == NULL) {
466 free(walk);
467 return GIT_ENOMEM;
468 }
469
470 git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp);
471 git_vector_init(&walk->memory_alloc, 8, NULL);
472 alloc_chunk(walk);
473
474 walk->get_next = &revwalk_next_unsorted;
475 walk->enqueue = &revwalk_enqueue_unsorted;
476
477 walk->repo = repo;
478
479 *revwalk_out = walk;
480 return GIT_SUCCESS;
481}
482
483void git_revwalk_free(git_revwalk *walk)
484{
485 unsigned int i;
402a47a7 486 const void *GIT_UNUSED(_unused);
21d73e71 487 commit_object *commit;
36aaf1ff
VM
488
489 if (walk == NULL)
490 return;
491
492 git_revwalk_reset(walk);
21d73e71
VM
493
494 /* if the parent has more than PARENTS_PER_COMMIT parents,
495 * we had to allocate a separate array for those parents.
496 * make sure it's being free'd */
497 GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, {
498 if (commit->out_degree > PARENTS_PER_COMMIT)
499 free(commit->parents);
500 });
501
36aaf1ff
VM
502 git_hashtable_free(walk->commits);
503 git_pqueue_free(&walk->iterator_time);
504
505 for (i = 0; i < walk->memory_alloc.length; ++i)
506 free(git_vector_get(&walk->memory_alloc, i));
507
508 git_vector_free(&walk->memory_alloc);
509 free(walk);
510}
511
512git_repository *git_revwalk_repository(git_revwalk *walk)
513{
514 assert(walk);
515 return walk->repo;
516}
517
518void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
519{
520 assert(walk);
521
522 if (walk->walking)
523 git_revwalk_reset(walk);
524
525 walk->sorting = sort_mode;
526
527 if (walk->sorting & GIT_SORT_TIME) {
528 walk->get_next = &revwalk_next_timesort;
529 walk->enqueue = &revwalk_enqueue_timesort;
530 } else {
531 walk->get_next = &revwalk_next_unsorted;
532 walk->enqueue = &revwalk_enqueue_unsorted;
533 }
534}
535
71db842f
VM
536int git_revwalk_next(git_oid *oid, git_revwalk *walk)
537{
538 int error;
539 commit_object *next;
3315782c 540
71db842f 541 assert(walk && oid);
3315782c 542
71db842f
VM
543 if (!walk->walking) {
544 if ((error = prepare_walk(walk)) < GIT_SUCCESS)
c0cd9d50 545 return git__rethrow(error, "Failed to load next revision");
71db842f 546 }
3315782c 547
71db842f 548 error = walk->get_next(&next, walk);
36aaf1ff
VM
549 if (error < GIT_SUCCESS) {
550 if (error == GIT_EREVWALKOVER)
551 git_revwalk_reset(walk);
c0cd9d50 552 return git__rethrow(error, "Failed to load next revision");
36aaf1ff 553 }
3315782c 554
71db842f
VM
555 git_oid_cpy(oid, &next->oid);
556 return GIT_SUCCESS;
3315782c
VM
557}
558
71db842f 559void git_revwalk_reset(git_revwalk *walk)
3315782c 560{
402a47a7 561 const void *GIT_UNUSED(_unused);
71db842f 562 commit_object *commit;
3315782c 563
71db842f 564 assert(walk);
9bdb7594 565
71db842f
VM
566 GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit,
567 commit->seen = 0;
568 commit->in_degree = 0;
569 commit->topo_delay = 0;
570 );
9bdb7594 571
36aaf1ff 572 git_pqueue_clear(&walk->iterator_time);
36b31329
VM
573 commit_list_free(&walk->iterator_topo);
574 commit_list_free(&walk->iterator_rand);
575 commit_list_free(&walk->iterator_reverse);
71db842f 576 walk->walking = 0;
08d5d000 577}
36b7cdb6 578