revwalk.c 14 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7
 */

8
#include "common.h"
9
#include "commit.h"
Vicent Marti committed
10
#include "odb.h"
11
#include "pool.h"
12

13
#include "revwalk.h"
14
#include "git2/revparse.h"
15
#include "merge.h"
16

17
GIT__USE_OIDMAP
18

19 20
git_commit_list_node *git_revwalk__commit_lookup(
	git_revwalk *walk, const git_oid *oid)
21
{
22
	git_commit_list_node *commit;
23 24
	khiter_t pos;
	int ret;
25

26 27 28 29
	/* lookup and reserve space if not already present */
	pos = kh_get(oid, walk->commits, oid);
	if (pos != kh_end(walk->commits))
		return kh_value(walk->commits, pos);
30

31
	commit = git_commit_list_alloc_node(walk);
32 33
	if (commit == NULL)
		return NULL;
34

35
	git_oid_cpy(&commit->oid, oid);
36

37 38 39
	pos = kh_put(oid, walk->commits, &commit->oid, &ret);
	assert(ret != 0);
	kh_value(walk->commits, pos) = commit;
40 41

	return commit;
42
}
43

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
typedef git_array_t(git_commit_list_node*) commit_list_node_array;

static bool interesting_arr(commit_list_node_array arr)
{
	git_commit_list_node **n;
	size_t i = 0, size;

	size = git_array_size(arr);
	for (i = 0; i < size; i++) {
		n = git_array_get(arr, i);
		if (!*n)
			break;

		if (!(*n)->uninteresting)
			return true;
	}

	return false;
}

64
static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
65
{
66
	int error;
67
	unsigned short i;
68
	commit_list_node_array pending = GIT_ARRAY_INIT;
69 70
	git_commit_list_node **tmp;

71
	assert(commit);
72

73 74 75
	do {
		commit->uninteresting = 1;

76 77
		if ((error = git_commit_list_parse(walk, commit)) < 0)
			return error;
78 79 80 81 82 83 84

		for (i = 0; i < commit->out_degree; ++i)
			if (!commit->parents[i]->uninteresting) {
				git_commit_list_node **node = git_array_alloc(pending);
				GITERR_CHECK_ALLOC(node);
				*node = commit->parents[i];
			}
85

86 87
		tmp = git_array_pop(pending);
		commit = tmp ? *tmp : NULL;
88

89
	} while (commit != NULL && !interesting_arr(pending));
90 91 92 93

	git_array_clear(pending);

	return 0;
94
}
95

96
static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide)
97
{
98 99
	int error;

100 101 102
	if (!hide && walk->hide_cb)
		hide = walk->hide_cb(&commit->oid, walk->hide_cb_payload);

103
	if (hide && mark_uninteresting(walk, commit) < 0)
104
		return -1;
105

106
	if (commit->seen)
107
		return 0;
108

109
	commit->seen = 1;
110

111
	if ((error = git_commit_list_parse(walk, commit)) < 0)
112
		return error;
113

114 115 116 117
	if (!hide)
		return walk->enqueue(walk, commit);

	return 0;
118
}
119

120
static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit)
121
{
122
	unsigned short i, max;
123
	int error = 0;
124

125 126 127 128 129
	max = commit->out_degree;
	if (walk->first_parent && commit->out_degree)
		max = 1;

	for (i = 0; i < max && !error; ++i)
130
		error = process_commit(walk, commit->parents[i], commit->uninteresting);
131

132
	return error;
133 134
}

135
static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob)
136
{
137
	git_oid commit_id;
138
	int error;
139
	git_object *obj, *oobj;
140
	git_commit_list_node *commit;
141
	git_commit_list *list;
142

143
	if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJ_ANY)) < 0)
144
		return error;
145

146 147
	error = git_object_peel(&obj, oobj, GIT_OBJ_COMMIT);
	git_object_free(oobj);
148

149
	if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) {
150 151 152 153
		/* If this comes from e.g. push_glob("tags"), ignore this */
		if (from_glob)
			return 0;

154
		giterr_set(GITERR_INVALID, "Object is not a committish");
155 156
		return -1;
	}
157 158 159 160 161
	if (error < 0)
		return error;

	git_oid_cpy(&commit_id, git_object_id(obj));
	git_object_free(obj);
162

163
	commit = git_revwalk__commit_lookup(walk, &commit_id);
164
	if (commit == NULL)
165
		return -1; /* error already reported by failed lookup */
166

167 168 169 170
	/* A previous hide already told us we don't want this commit  */
	if (commit->uninteresting)
		return 0;

171 172 173 174 175
	if (uninteresting)
		walk->did_hide = 1;
	else
		walk->did_push = 1;

176
	commit->uninteresting = uninteresting;
177 178 179 180
	list = walk->user_input;
	if (git_commit_list_insert(commit, &list) == NULL) {
		giterr_set_oom();
		return -1;
181 182
	}

183 184
	walk->user_input = list;

185
	return 0;
186 187
}

188 189 190
int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
{
	assert(walk && oid);
191
	return push_commit(walk, oid, 0, false);
192
}
193

194

195 196 197
int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
{
	assert(walk && oid);
198
	return push_commit(walk, oid, 1, false);
199
}
200

201
static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob)
202
{
203
	git_oid oid;
204

205
	if (git_reference_name_to_id(&oid, walk->repo, refname) < 0)
206 207
		return -1;

208
	return push_commit(walk, &oid, hide, from_glob);
209 210
}

211 212
static int push_glob(git_revwalk *walk, const char *glob, int hide)
{
213
	int error = 0;
214
	git_buf buf = GIT_BUF_INIT;
215 216
	git_reference *ref;
	git_reference_iterator *iter;
217
	size_t wildcard;
218 219 220 221

	assert(walk && glob);

	/* refs/ is implied if not given in the glob */
222 223 224
	if (git__prefixcmp(glob, GIT_REFS_DIR) != 0)
		git_buf_joinpath(&buf, GIT_REFS_DIR, glob);
	else
225
		git_buf_puts(&buf, glob);
226
	GITERR_CHECK_ALLOC_BUF(&buf);
227 228

	/* If no '?', '*' or '[' exist, we append '/ *' to the glob */
229 230 231
	wildcard = strcspn(glob, "?*[");
	if (!glob[wildcard])
		git_buf_put(&buf, "/*", 2);
232

233 234
	if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0)
		goto out;
235

236 237 238 239 240 241 242
	while ((error = git_reference_next(&ref, iter)) == 0) {
		error = push_ref(walk, git_reference_name(ref), hide, true);
		git_reference_free(ref);
		if (error < 0)
			break;
	}
	git_reference_iterator_free(iter);
243

244 245 246
	if (error == GIT_ITEROVER)
		error = 0;
out:
247
	git_buf_free(&buf);
248
	return error;
249 250 251 252 253 254 255 256 257 258 259 260 261 262
}

int git_revwalk_push_glob(git_revwalk *walk, const char *glob)
{
	assert(walk && glob);
	return push_glob(walk, glob, 0);
}

int git_revwalk_hide_glob(git_revwalk *walk, const char *glob)
{
	assert(walk && glob);
	return push_glob(walk, glob, 1);
}

263 264 265
int git_revwalk_push_head(git_revwalk *walk)
{
	assert(walk);
266
	return push_ref(walk, GIT_HEAD_FILE, 0, false);
267 268 269 270 271
}

int git_revwalk_hide_head(git_revwalk *walk)
{
	assert(walk);
272
	return push_ref(walk, GIT_HEAD_FILE, 1, false);
273 274 275 276 277
}

int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
{
	assert(walk && refname);
278
	return push_ref(walk, refname, 0, false);
279 280
}

281 282
int git_revwalk_push_range(git_revwalk *walk, const char *range)
{
283
	git_revspec revspec;
284 285
	int error = 0;

286
	if ((error = git_revparse(&revspec, walk->repo, range)))
287
		return error;
Vicent Marti committed
288

289
	if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
290 291 292 293 294
		/* TODO: support "<commit>...<commit>" */
		giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");
		return GIT_EINVALIDSPEC;
	}

295
	if ((error = push_commit(walk, git_object_id(revspec.from), 1, false)))
296 297
		goto out;

298
	error = push_commit(walk, git_object_id(revspec.to), 0, false);
Vicent Marti committed
299 300

out:
301 302
	git_object_free(revspec.from);
	git_object_free(revspec.to);
303 304 305
	return error;
}

306 307 308
int git_revwalk_hide_ref(git_revwalk *walk, const char *refname)
{
	assert(walk && refname);
309
	return push_ref(walk, refname, 1, false);
310 311
}

312
static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit)
313
{
314 315
	return git_pqueue_insert(&walk->iterator_time, commit);
}
316

317
static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit)
318
{
319
	return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
320
}
321

322
static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk)
323 324
{
	int error;
325
	git_commit_list_node *next;
326

327
	while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL)
328
		if (!next->uninteresting) {
329 330 331
			if ((error = process_commit_parents(walk, next)) < 0)
				return error;

332
			*object_out = next;
333
			return 0;
334
		}
335

Russell Belfer committed
336 337
	giterr_clear();
	return GIT_ITEROVER;
338 339
}

340
static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk)
341
{
342
	int error;
343
	git_commit_list_node *next;
344

345
	while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL)
346
		if (!next->uninteresting) {
347 348 349
			if ((error = process_commit_parents(walk, next)) < 0)
				return error;

350
			*object_out = next;
351
			return 0;
352
		}
353

Russell Belfer committed
354 355
	giterr_clear();
	return GIT_ITEROVER;
356
}
357

358
static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk)
359
{
360
	git_commit_list_node *next;
361
	unsigned short i, max;
362

363
	for (;;) {
364
		next = git_commit_list_pop(&walk->iterator_topo);
Russell Belfer committed
365 366 367 368
		if (next == NULL) {
			giterr_clear();
			return GIT_ITEROVER;
		}
369

370 371 372 373
		if (next->in_degree > 0) {
			next->topo_delay = 1;
			continue;
		}
374

375 376 377 378 379 380

		max = next->out_degree;
		if (walk->first_parent && next->out_degree)
			max = 1;

		for (i = 0; i < max; ++i) {
381
			git_commit_list_node *parent = next->parents[i];
382

383 384
			if (--parent->in_degree == 0 && parent->topo_delay) {
				parent->topo_delay = 0;
385
				if (git_commit_list_insert(parent, &walk->iterator_topo) == NULL)
386
					return -1;
387 388
			}
		}
389

390
		*object_out = next;
391
		return 0;
392
	}
393 394
}

395
static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk)
396
{
397
	*object_out = git_commit_list_pop(&walk->iterator_reverse);
Russell Belfer committed
398
	return *object_out ? 0 : GIT_ITEROVER;
399
}
400

401

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
static int interesting(git_pqueue *list)
{
	size_t i;

	for (i = 0; i < git_pqueue_size(list); i++) {
		git_commit_list_node *commit = git_pqueue_get(list, i);
		if (!commit->uninteresting)
			return 1;
	}

	return 0;
}

static int contains(git_pqueue *list, git_commit_list_node *node)
{
	size_t i;

	for (i = 0; i < git_pqueue_size(list); i++) {
		git_commit_list_node *commit = git_pqueue_get(list, i);
		if (commit == node)
			return 1;
	}

	return 0;
}

static int premark_uninteresting(git_revwalk *walk)
{
	int error = 0;
	unsigned short i;
	git_pqueue q;
	git_commit_list *list;
	git_commit_list_node *commit, *parent;

	if ((error = git_pqueue_init(&q, 0, 8, git_commit_list_time_cmp)) < 0)
		return error;

	for (list = walk->user_input; list; list = list->next) {
		if ((error = git_commit_list_parse(walk, list->item)) < 0)
			goto cleanup;

		if ((error = git_pqueue_insert(&q, list->item)) < 0)
			goto cleanup;
	}

	while (interesting(&q)) {
		commit = git_pqueue_pop(&q);

		for (i = 0; i < commit->out_degree; i++) {
			parent = commit->parents[i];

			if ((error = git_commit_list_parse(walk, parent)) < 0)
				goto cleanup;

			if (commit->uninteresting)
				parent->uninteresting = 1;

			if (contains(&q, parent))
				continue;

			if ((error = git_pqueue_insert(&q, parent)) < 0)
				goto cleanup;
		}
	}

cleanup:
	git_pqueue_free(&q);
	return error;
}

472 473
static int prepare_walk(git_revwalk *walk)
{
474
	int error;
475 476 477
	git_commit_list *list;
	git_commit_list_node *next;

478 479 480 481 482 483 484
	/* If there were no pushes, we know that the walk is already over */
	if (!walk->did_push) {
		giterr_clear();
		return GIT_ITEROVER;
	}

	if (walk->did_hide && (error = premark_uninteresting(walk)) < 0)
485 486
		return error;

487 488 489 490 491
	for (list = walk->user_input; list; list = list->next) {
		if (process_commit(walk, list->item, list->item->uninteresting) < 0)
			return -1;
	}

492

493 494
	if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
		unsigned short i;
495

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];
499 500
				parent->in_degree++;
			}
501

502
			if (git_commit_list_insert(next, &walk->iterator_topo) == NULL)
503
				return -1;
504
		}
505

Russell Belfer committed
506
		if (error != GIT_ITEROVER)
507
			return error;
508

509 510
		walk->get_next = &revwalk_next_toposort;
	}
511

512
	if (walk->sorting & GIT_SORT_REVERSE) {
513

514
		while ((error = walk->get_next(&next, walk)) == 0)
515
			if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL)
516
				return -1;
517

Russell Belfer committed
518
		if (error != GIT_ITEROVER)
519
			return error;
520

521 522
		walk->get_next = &revwalk_next_reverse;
	}
523

524
	walk->walking = 1;
525
	return 0;
526
}
527 528


529 530
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);
533

534
	walk->commits = git_oidmap_alloc();
535
	GITERR_CHECK_ALLOC(walk->commits);
536

537
	if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0)
538
		return -1;
539

540
	git_pool_init(&walk->commit_pool, COMMIT_ALLOC);
541 542 543 544 545
	walk->get_next = &revwalk_next_unsorted;
	walk->enqueue = &revwalk_enqueue_unsorted;

	walk->repo = repo;

546
	if (git_repository_odb(&walk->odb, repo) < 0) {
547
		git_revwalk_free(walk);
548
		return -1;
549 550
	}

551
	*revwalk_out = walk;
552
	return 0;
553 554 555 556 557 558 559 560
}

void git_revwalk_free(git_revwalk *walk)
{
	if (walk == NULL)
		return;

	git_revwalk_reset(walk);
561
	git_odb_free(walk->odb);
562

563
	git_oidmap_free(walk->commits);
564
	git_pool_clear(&walk->commit_pool);
565
	git_pqueue_free(&walk->iterator_time);
566
	git__free(walk);
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
}

git_repository *git_revwalk_repository(git_revwalk *walk)
{
	assert(walk);
	return walk->repo;
}

void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
{
	assert(walk);

	if (walk->walking)
		git_revwalk_reset(walk);

	walk->sorting = sort_mode;

	if (walk->sorting & GIT_SORT_TIME) {
		walk->get_next = &revwalk_next_timesort;
		walk->enqueue = &revwalk_enqueue_timesort;
	} else {
		walk->get_next = &revwalk_next_unsorted;
		walk->enqueue = &revwalk_enqueue_unsorted;
	}
}

593 594 595 596 597
void git_revwalk_simplify_first_parent(git_revwalk *walk)
{
	walk->first_parent = 1;
}

598 599 600
int git_revwalk_next(git_oid *oid, git_revwalk *walk)
{
	int error;
601
	git_commit_list_node *next;
602

603
	assert(walk && oid);
604

605
	if (!walk->walking) {
606
		if ((error = prepare_walk(walk)) < 0)
607
			return error;
608
	}
609

610
	error = walk->get_next(&next, walk);
611

Russell Belfer committed
612
	if (error == GIT_ITEROVER) {
613
		git_revwalk_reset(walk);
Russell Belfer committed
614 615
		giterr_clear();
		return GIT_ITEROVER;
616
	}
617

618 619
	if (!error)
		git_oid_cpy(oid, &next->oid);
620

621
	return error;
622 623
}

624
void git_revwalk_reset(git_revwalk *walk)
625
{
626
	git_commit_list_node *commit;
627

628
	assert(walk);
629

630
	kh_foreach_value(walk->commits, commit, {
631 632 633
		commit->seen = 0;
		commit->in_degree = 0;
		commit->topo_delay = 0;
634
		commit->uninteresting = 0;
635
		commit->flags = 0;
636
		});
637

638
	git_pqueue_clear(&walk->iterator_time);
639 640 641
	git_commit_list_free(&walk->iterator_topo);
	git_commit_list_free(&walk->iterator_rand);
	git_commit_list_free(&walk->iterator_reverse);
642
	git_commit_list_free(&walk->user_input);
643
	walk->first_parent = 0;
644
	walk->walking = 0;
645
	walk->did_push = walk->did_hide = 0;
646
}
647

648 649 650 651 652 653 654 655 656 657
int git_revwalk_add_hide_cb(
	git_revwalk *walk,
	git_revwalk_hide_cb hide_cb,
	void *payload)
{
	assert(walk);

	if (walk->walking)
		git_revwalk_reset(walk);

658
	if (walk->hide_cb) {
659 660 661 662 663 664 665 666 667 668 669
		/* There is already a callback added */
		giterr_set(GITERR_INVALID, "There is already a callback added to hide commits in revision walker.");
		return -1;
	}

	walk->hide_cb = hide_cb;
	walk->hide_cb_payload = payload;

	return 0;
}