revwalk.c 9.35 KB
Newer Older
1
/*
2 3 4
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
5
 *
6 7 8 9 10 11 12 13
 * In addition to the permissions in the GNU General Public License,
 * the authors give you unlimited permission to link the compiled
 * version of this file into combinations with other programs,
 * and to distribute those combinations without any restriction
 * coming from the use of this file.  (The General Public License
 * restrictions do apply in other respects; for example, they cover
 * modification of the file, and distribution when not linked into
 * a combined executable.)
14
 *
15 16 17 18
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
19
 *
20 21 22 23
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
24 25
 */

26
#include "common.h"
27
#include "commit.h"
28
#include "revwalk.h"
29
#include "hashtable.h"
30

31
uint32_t git_revwalk__commit_hash(const void *key, int hash_id)
32 33 34
{
	uint32_t r;
	git_commit *commit;
35

36
	commit = (git_commit *)key;
37
	memcpy(&r, commit->object.id.id + (hash_id * sizeof(uint32_t)), sizeof(r));
38 39 40
	return r;
}

41
int git_revwalk__commit_keycmp(const void *key_a, const void *key_b)
42
{
43 44 45
	git_commit *a = (git_commit *)key_a;
	git_commit *b = (git_commit *)key_b;
	return git_oid_cmp(&a->object.id, &b->object.id);
46 47
}

Vicent Marti committed
48
int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
49 50 51 52 53
{
	git_revwalk *walk;

	walk = git__malloc(sizeof(git_revwalk));
	if (walk == NULL)
Vicent Marti committed
54
		return GIT_ENOMEM;
55

56
	memset(walk, 0x0, sizeof(git_revwalk));
57

58
	walk->commits = git_hashtable_alloc(64,
Vicent Marti committed
59
			git_revwalk__commit_hash,
60
			git_revwalk__commit_keycmp);
61 62 63

	if (walk->commits == NULL) {
		free(walk);
Vicent Marti committed
64
		return GIT_ENOMEM;
65
	}
66

67
	walk->repo = repo;
Vicent Marti committed
68 69 70

	*revwalk_out = walk;
	return GIT_SUCCESS;
71 72
}

73
void git_revwalk_free(git_revwalk *walk)
74
{
75 76 77
	if (walk == NULL)
		return;

78 79 80 81
	git_revwalk_reset(walk);
	git_hashtable_free(walk->commits);
	free(walk);
}
82

83 84 85 86 87 88
git_repository *git_revwalk_repository(git_revwalk *walk)
{
	assert(walk);
	return walk->repo;
}

Vicent Marti committed
89
int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
90
{
91 92
	assert(walk);

93
	if (walk->walking)
Vicent Marti committed
94
		return GIT_EBUSY;
95

96 97
	walk->sorting = sort_mode;
	git_revwalk_reset(walk);
Vicent Marti committed
98
	return GIT_SUCCESS;
99
}
100

101 102 103
static git_revwalk_commit *commit_to_walkcommit(git_revwalk *walk, git_commit *commit_object)
{
	git_revwalk_commit *commit;
104

105
	commit = (git_revwalk_commit *)git_hashtable_lookup(walk->commits, commit_object);
106

107 108
	if (commit != NULL)
		return commit;
109

110 111 112
	commit = git__malloc(sizeof(git_revwalk_commit));
	if (commit == NULL)
		return NULL;
113

114 115 116
	memset(commit, 0x0, sizeof(git_revwalk_commit));

	commit->commit_object = commit_object;
117
	GIT_OBJECT_INCREF(walk->repo, commit_object);
118 119 120 121

	git_hashtable_insert(walk->commits, commit_object, commit);

	return commit;
122
}
123

124
static git_revwalk_commit *insert_commit(git_revwalk *walk, git_commit *commit_object)
125
{
126
	git_revwalk_commit *commit;
127
	unsigned int i;
128 129 130 131 132 133 134 135 136

	assert(walk && commit_object);

	if (commit_object->object.repo != walk->repo || walk->walking)
		return NULL;

	commit = commit_to_walkcommit(walk, commit_object);
	if (commit == NULL)
		return NULL;
137

138 139 140 141 142
	if (commit->seen)
		return commit;

	commit->seen = 1;

143 144
	for (i = 0; i < commit->commit_object->parents.length; ++i) {
		git_commit *parent_object;
145 146
		git_revwalk_commit *parent;

147 148 149
		parent_object = git_vector_get(&commit->commit_object->parents, i);

		if ((parent = commit_to_walkcommit(walk, parent_object)) == NULL)
150 151
			return NULL;

152
		parent = insert_commit(walk, parent_object);
153 154 155 156 157 158 159 160 161 162 163 164
		if (parent == NULL)
			return NULL;

		parent->in_degree++;

		git_revwalk_list_push_back(&commit->parents, parent);
	}

	if (git_revwalk_list_push_back(&walk->iterator, commit))
		return NULL;

	return commit;
165 166
}

167
int git_revwalk_push(git_revwalk *walk, git_commit *commit)
168
{
169
	assert(walk && commit);
Vicent Marti committed
170
	return insert_commit(walk, commit) ? GIT_SUCCESS : GIT_ENOMEM;
171
}
172

173 174 175 176
static void mark_uninteresting(git_revwalk_commit *commit)
{
	git_revwalk_listnode *parent;

Vicent Marti committed
177
	assert(commit);
178

179 180
	commit->uninteresting = 1;
	parent = commit->parents.head;
181

182 183 184
	while (parent) {
		mark_uninteresting(parent->walk_commit);
		parent = parent->next;
185
	}
186 187 188 189 190
}

int git_revwalk_hide(git_revwalk *walk, git_commit *commit)
{
	git_revwalk_commit *hide;
191 192

	assert(walk && commit);
193 194 195
	
	hide = insert_commit(walk, commit);
	if (hide == NULL)
Vicent Marti committed
196
		return GIT_ENOMEM;
197

198 199 200
	mark_uninteresting(hide);
	return GIT_SUCCESS;
}
201

202

203 204 205 206 207 208 209 210 211 212 213 214 215 216
static void prepare_walk(git_revwalk *walk)
{
	if (walk->sorting & GIT_SORT_TIME)
		git_revwalk_list_timesort(&walk->iterator);

	if (walk->sorting & GIT_SORT_TOPOLOGICAL)
		git_revwalk_list_toposort(&walk->iterator);

	if (walk->sorting & GIT_SORT_REVERSE)
		walk->next = &git_revwalk_list_pop_back;
	else
		walk->next = &git_revwalk_list_pop_front;

	walk->walking = 1;
217 218
}

219
int git_revwalk_next(git_commit **commit, git_revwalk *walk)
220
{
221
	git_revwalk_commit *next;
222

223
	assert(walk && commit);
224

225 226 227
	if (!walk->walking)
		prepare_walk(walk);

228 229
	*commit = NULL;

230
	while ((next = walk->next(&walk->iterator)) != NULL) {
231 232
		if (!next->uninteresting) {
			*commit = next->commit_object;
233
			GIT_OBJECT_INCREF(walk->repo, *commit);
234 235
			return GIT_SUCCESS;
		}
236 237 238 239
	}

	/* No commits left to iterate */
	git_revwalk_reset(walk);
240
	return GIT_EREVWALKOVER;
241 242
}

243
void git_revwalk_reset(git_revwalk *walk)
244
{
245
	const void *_unused;
246
	git_revwalk_commit *commit;
247

248 249
	assert(walk);

250
	GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, {
251
		GIT_OBJECT_DECREF(walk->repo, commit->commit_object);
252 253
		git_revwalk_list_clear(&commit->parents);
		free(commit);
254
	});
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	git_hashtable_clear(walk->commits);
	git_revwalk_list_clear(&walk->iterator);
	walk->walking = 0;
}






int git_revwalk_list_push_back(git_revwalk_list *list, git_revwalk_commit *commit)
{
	git_revwalk_listnode *node = NULL;

	node = git__malloc(sizeof(git_revwalk_listnode));

	if (node == NULL)
		return GIT_ENOMEM;
274

275 276 277
	node->walk_commit = commit;
	node->next = NULL;
	node->prev = list->tail;
278

279 280 281 282 283
	if (list->tail == NULL) {
		list->head = list->tail = node;
	} else {
		list->tail->next = node;
		list->tail = node;
284
	}
285

286 287 288 289 290 291 292 293 294 295 296
	list->size++;
	return 0;
}

int git_revwalk_list_push_front(git_revwalk_list *list, git_revwalk_commit *commit)
{
	git_revwalk_listnode *node = NULL;

	node = git__malloc(sizeof(git_revwalk_listnode));

	if (node == NULL)
297 298
		return GIT_ENOMEM;

299 300 301 302 303 304 305 306 307 308 309 310
	node->walk_commit = commit;
	node->next = list->head;
	node->prev = NULL;

	if (list->head == NULL) {
		list->head = list->tail = node;
	} else {
		list->head->prev = node;
		list->head = node;
	}

	list->size++;
311
	return 0;
312
}
313

314 315

git_revwalk_commit *git_revwalk_list_pop_back(git_revwalk_list *list)
316
{
317 318
	git_revwalk_listnode *node;
	git_revwalk_commit *commit;
319

320 321
	if (list->tail == NULL)
		return NULL;
322

323 324 325 326
	node = list->tail;
	list->tail = list->tail->prev;
	if (list->tail == NULL)
		list->head = NULL;
327 328
	else
		list->tail->next = NULL;
329

330 331
	commit = node->walk_commit;
	free(node);
332

333
	list->size--;
334

335
	return commit;
336 337
}

338
git_revwalk_commit *git_revwalk_list_pop_front(git_revwalk_list *list)
339
{
340 341 342 343 344
	git_revwalk_listnode *node;
	git_revwalk_commit *commit;

	if (list->head == NULL)
		return NULL;
345

346 347 348 349
	node = list->head;
	list->head = list->head->next;
	if (list->head == NULL)
		list->tail = NULL;
350 351
	else
		list->head->prev = NULL;
352

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
	commit = node->walk_commit;
	free(node);

	list->size--;

	return commit;
}

void git_revwalk_list_clear(git_revwalk_list *list)
{
	git_revwalk_listnode *node, *next_node;

	node = list->head;
	while (node) {
		next_node = node->next;
		free(node);
		node = next_node;
370
	}
371

372 373
	list->head = list->tail = NULL;
	list->size = 0;
374 375
}

376
void git_revwalk_list_timesort(git_revwalk_list *list)
377
{
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
	git_revwalk_listnode *p, *q, *e;
	int in_size, p_size, q_size, merge_count, i;

	if (list->head == NULL)
		return;

	in_size = 1;

	do {
		p = list->head;
		list->tail = NULL;
		merge_count = 0;

		while (p != NULL) {
			merge_count++;
			q = p;
			p_size = 0;
			q_size = in_size;

			for (i = 0; i < in_size && q; ++i, q = q->next)
				p_size++;

			while (p_size > 0 || (q_size > 0 && q)) {
401

402 403
				if (p_size == 0)
					e = q, q = q->next, q_size--;
404

405
				else if (q_size == 0 || q == NULL ||
406 407
						p->walk_commit->commit_object->committer->when.time >= 
						q->walk_commit->commit_object->committer->when.time)
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
					e = p, p = p->next, p_size--;

				else
					e = q, q = q->next, q_size--;

				if (list->tail != NULL)
					list->tail->next = e;
				else
					list->head = e;

				e->prev = list->tail;
				list->tail = e;
			}

			p = q;
		}

		list->tail->next = NULL;
		in_size *= 2;

	} while (merge_count > 1);
}

void git_revwalk_list_toposort(git_revwalk_list *list)
{
	git_revwalk_commit *commit;
	git_revwalk_list topo;
	memset(&topo, 0x0, sizeof(git_revwalk_list));

	while ((commit = git_revwalk_list_pop_back(list)) != NULL) {
		git_revwalk_listnode *p;

		if (commit->in_degree > 0) {
			commit->topo_delay = 1;
			continue;
		}

		for (p = commit->parents.head; p != NULL; p = p->next) {
			p->walk_commit->in_degree--;

			if (p->walk_commit->in_degree == 0 && p->walk_commit->topo_delay) {
				p->walk_commit->topo_delay = 0;
				git_revwalk_list_push_back(list, p->walk_commit);
			}
		}
453

454
		git_revwalk_list_push_back(&topo, commit);
455
	}
456

457 458 459
	list->head = topo.head;
	list->tail = topo.tail;
	list->size = topo.size;
460
}
461