iterator.c 11.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*
 * Copyright (C) 2009-2012 the libgit2 contributors
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "iterator.h"
#include "tree.h"
#include "ignore.h"
#include "buffer.h"

13 14 15 16 17 18
typedef struct tree_iterator_frame tree_iterator_frame;
struct tree_iterator_frame {
	tree_iterator_frame *next;
	git_tree *tree;
	unsigned int index;
};
19 20

typedef struct {
21
	git_iterator base;
22
	git_repository *repo;
23
	tree_iterator_frame *stack;
24 25
	git_index_entry entry;
	git_buf path;
26
} tree_iterator;
27

28
static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti)
29
{
30 31
	return (ti->stack == NULL) ? NULL :
		git_tree_entry_byindex(ti->stack->tree, ti->stack->index);
32 33
}

34
static int tree_iterator__current(
35 36 37
	git_iterator *self, const git_index_entry **entry)
{
	int error;
38 39
	tree_iterator *ti = (tree_iterator *)self;
	const git_tree_entry *te = tree_iterator__tree_entry(ti);
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

	*entry = NULL;

	if (te == NULL)
		return GIT_SUCCESS;

	ti->entry.mode = te->attr;
	git_oid_cpy(&ti->entry.oid, &te->oid);
	error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
	if (error < GIT_SUCCESS)
		return error;
	ti->entry.path = ti->path.ptr;

	*entry = &ti->entry;

	return GIT_SUCCESS;
}

58
static int tree_iterator__at_end(git_iterator *self)
59
{
60
	return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
61 62
}

63
static tree_iterator_frame *tree_iterator__alloc_frame(git_tree *tree)
64
{
65 66 67 68
	tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame));
	tf->tree = tree;
	return tf;
}
69

70 71 72 73 74 75
static int tree_iterator__expand_tree(tree_iterator *ti)
{
	int error;
	git_tree *subtree;
	const git_tree_entry *te = tree_iterator__tree_entry(ti);
	tree_iterator_frame *tf;
76

77
	while (te != NULL && entry_is_tree(te)) {
78 79 80 81
		error = git_tree_lookup(&subtree, ti->repo, &te->oid);
		if (error != GIT_SUCCESS)
			return error;

82 83 84 85 86 87 88 89
		if ((tf = tree_iterator__alloc_frame(subtree)) == NULL)
			return GIT_ENOMEM;

		tf->next  = ti->stack;
		ti->stack = tf;

		error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
		if (error < GIT_SUCCESS)
90
			return error;
91 92

		te = tree_iterator__tree_entry(ti);
93 94 95 96 97
	}

	return GIT_SUCCESS;
}

98 99 100 101 102 103 104 105 106 107
static void tree_iterator__pop_frame(tree_iterator *ti)
{
	tree_iterator_frame *tf = ti->stack;
	ti->stack = tf->next;
	if (ti->stack != NULL) /* don't free the initial tree */
		git_tree_free(tf->tree);
	git__free(tf);
}

static int tree_iterator__advance(
108
	git_iterator *self, const git_index_entry **entry)
109
{
110
	int error = GIT_SUCCESS;
111
	tree_iterator *ti = (tree_iterator *)self;
schu committed
112
	const git_tree_entry *te = NULL;
113

114 115 116
	if (entry != NULL)
		*entry = NULL;

117
	while (ti->stack != NULL) {
118 119 120
		/* remove old entry filename */
		git_buf_rtruncate_at_char(&ti->path, '/');

121 122
		te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index);
		if (te != NULL)
123 124
			break;

125
		tree_iterator__pop_frame(ti);
126 127 128 129
		git_buf_rtruncate_at_char(&ti->path, '/');
	}

	if (te && entry_is_tree(te))
130
		error = tree_iterator__expand_tree(ti);
131

132
	if (error == GIT_SUCCESS && entry != NULL)
133
		error = tree_iterator__current(self, entry);
134 135

	return error;
136 137
}

138
static void tree_iterator__free(git_iterator *self)
139
{
140 141 142
	tree_iterator *ti = (tree_iterator *)self;
	while (ti->stack != NULL)
		tree_iterator__pop_frame(ti);
143 144 145
	git_buf_free(&ti->path);
}

146 147
int git_iterator_for_tree(
	git_repository *repo, git_tree *tree, git_iterator **iter)
148 149
{
	int error;
150
	tree_iterator *ti = git__calloc(1, sizeof(tree_iterator));
151 152 153
	if (!ti)
		return GIT_ENOMEM;

154 155 156 157 158 159 160
	ti->base.type    = GIT_ITERATOR_TREE;
	ti->base.current = tree_iterator__current;
	ti->base.at_end  = tree_iterator__at_end;
	ti->base.advance = tree_iterator__advance;
	ti->base.free    = tree_iterator__free;
	ti->repo         = repo;
	ti->stack        = tree_iterator__alloc_frame(tree);
161

162
	if ((error = tree_iterator__expand_tree(ti)) < GIT_SUCCESS)
163 164 165 166 167 168 169 170 171
		git_iterator_free((git_iterator *)ti);
	else
		*iter = (git_iterator *)ti;

	return error;
}


typedef struct {
172
	git_iterator base;
173 174
	git_index *index;
	unsigned int current;
175
} index_iterator;
176

177
static int index_iterator__current(
178 179
	git_iterator *self, const git_index_entry **entry)
{
180
	index_iterator *ii = (index_iterator *)self;
181 182 183 184
	*entry = git_index_get(ii->index, ii->current);
	return GIT_SUCCESS;
}

185
static int index_iterator__at_end(git_iterator *self)
186
{
187
	index_iterator *ii = (index_iterator *)self;
188 189 190
	return (ii->current >= git_index_entrycount(ii->index));
}

191
static int index_iterator__advance(
192
	git_iterator *self, const git_index_entry **entry)
193
{
194
	index_iterator *ii = (index_iterator *)self;
195 196
	if (ii->current < git_index_entrycount(ii->index))
		ii->current++;
197 198
	if (entry)
		*entry = git_index_get(ii->index, ii->current);
199 200 201
	return GIT_SUCCESS;
}

202
static void index_iterator__free(git_iterator *self)
203
{
204
	index_iterator *ii = (index_iterator *)self;
205 206 207 208 209 210 211
	git_index_free(ii->index);
	ii->index = NULL;
}

int git_iterator_for_index(git_repository *repo, git_iterator **iter)
{
	int error;
212
	index_iterator *ii = git__calloc(1, sizeof(index_iterator));
213 214 215
	if (!ii)
		return GIT_ENOMEM;

216 217 218 219 220 221
	ii->base.type    = GIT_ITERATOR_INDEX;
	ii->base.current = index_iterator__current;
	ii->base.at_end  = index_iterator__at_end;
	ii->base.advance = index_iterator__advance;
	ii->base.free    = index_iterator__free;
	ii->current      = 0;
222 223 224 225 226 227 228 229 230

	if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS)
		git__free(ii);
	else
		*iter = (git_iterator *)ii;
	return error;
}


231 232 233 234 235 236 237
typedef struct workdir_iterator_frame workdir_iterator_frame;
struct workdir_iterator_frame {
	workdir_iterator_frame *next;
	git_vector entries;
	unsigned int index;
};

238
typedef struct {
239
	git_iterator base;
240 241
	git_repository *repo;
	size_t root_len;
242
	workdir_iterator_frame *stack;
243 244 245 246
	git_ignores ignores;
	git_index_entry entry;
	git_buf path;
	int is_ignored;
247 248 249 250 251 252 253 254 255 256 257 258 259
} workdir_iterator;

static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
{
	workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
	if (wf == NULL)
		return wf;
	if (git_vector_init(&wf->entries, 0, git__strcmp_cb) != GIT_SUCCESS) {
		git__free(wf);
		return NULL;
	}
	return wf;
}
260

261
static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
262 263 264 265
{
	unsigned int i;
	char *path;

266
	git_vector_foreach(&wf->entries, i, path)
267
		git__free(path);
268 269
	git_vector_free(&wf->entries);
	git__free(wf);
270 271
}

272
static int workdir_iterator__update_entry(workdir_iterator *wi);
273

274
static int workdir_iterator__expand_dir(workdir_iterator *wi)
275 276
{
	int error;
277 278 279
	workdir_iterator_frame *wf = workdir_iterator__alloc_frame();
	if (wf == NULL)
		return GIT_ENOMEM;
280

281 282
	/* allocate dir entries with extra byte (the "1" param) so we
	 * can suffix directory names with a "/".
283
	 */
284 285 286
	error = git_path_dirload(wi->path.ptr, wi->root_len, 1, &wf->entries);
	if (error < GIT_SUCCESS || wf->entries.length == 0) {
		workdir_iterator__free_frame(wf);
287 288 289
		return GIT_ENOTFOUND;
	}

290 291 292
	git_vector_sort(&wf->entries);
	wf->next  = wi->stack;
	wi->stack = wf;
293

294 295
	/* only push new ignores if this is not top level directory */
	if (wi->stack->next != NULL) {
296 297 298 299
		int slash_pos = git_buf_rfind_next(&wi->path, '/');
		(void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);
	}

300
	return workdir_iterator__update_entry(wi);
301 302
}

303
static int workdir_iterator__current(
304 305
	git_iterator *self, const git_index_entry **entry)
{
306
	workdir_iterator *wi = (workdir_iterator *)self;
307 308 309 310
	*entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
	return GIT_SUCCESS;
}

311
static int workdir_iterator__at_end(git_iterator *self)
312
{
313
	return (((workdir_iterator *)self)->entry.path == NULL);
314 315
}

316
static int workdir_iterator__advance(
317
	git_iterator *self, const git_index_entry **entry)
318
{
319
	int error;
320 321
	workdir_iterator *wi = (workdir_iterator *)self;
	workdir_iterator_frame *wf;
322 323
	const char *next;

324
	if (entry != NULL)
325 326
		*entry = NULL;

327 328 329
	if (wi->entry.path == NULL)
		return GIT_SUCCESS;

330 331
	while ((wf = wi->stack) != NULL) {
		next = git_vector_get(&wf->entries, ++wf->index);
332 333 334 335 336 337 338
		if (next != NULL) {
			if (strcmp(next, DOT_GIT) == 0)
				continue;
			/* else found a good entry */
			break;
		}

339 340 341
		/* pop workdir directory stack */
		wi->stack = wf->next;
		workdir_iterator__free_frame(wf);
342
		git_ignore__pop_dir(&wi->ignores);
343 344 345 346 347

		if (wi->stack == NULL) {
			memset(&wi->entry, 0, sizeof(wi->entry));
			return GIT_SUCCESS;
		}
348 349
	}

350
	error = workdir_iterator__update_entry(wi);
351

352 353
	if (error == GIT_SUCCESS && entry != NULL)
		error = workdir_iterator__current(self, entry);
354 355

	return error;
356 357
}

358
static void workdir_iterator__free(git_iterator *self)
359
{
360
	workdir_iterator *wi = (workdir_iterator *)self;
361

362 363 364 365
	while (wi->stack != NULL) {
		workdir_iterator_frame *wf = wi->stack;
		wi->stack = wf->next;
		workdir_iterator__free_frame(wf);
366 367 368 369 370 371
	}

	git_ignore__free(&wi->ignores);
	git_buf_free(&wi->path);
}

372
static int workdir_iterator__update_entry(workdir_iterator *wi)
373 374 375
{
	int error;
	struct stat st;
376
	char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index);
377 378 379 380 381 382 383 384 385

	error = git_buf_joinpath(
		&wi->path, git_repository_workdir(wi->repo), relpath);
	if (error < GIT_SUCCESS)
		return error;

	memset(&wi->entry, 0, sizeof(wi->entry));
	wi->entry.path = relpath;

386
	/* skip over .git directory */
387
	if (strcmp(relpath, DOT_GIT) == 0)
388
		return workdir_iterator__advance((git_iterator *)wi, NULL);
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409

	/* if there is an error processing the entry, treat as ignored */
	wi->is_ignored = 1;

	if (p_lstat(wi->path.ptr, &st) < 0)
		return GIT_SUCCESS;

	/* TODO: remove shared code for struct stat conversion with index.c */
	wi->entry.ctime.seconds = (git_time_t)st.st_ctime;
	wi->entry.mtime.seconds = (git_time_t)st.st_mtime;
	wi->entry.dev  = st.st_rdev;
	wi->entry.ino  = st.st_ino;
	wi->entry.mode = git_futils_canonical_mode(st.st_mode);
	wi->entry.uid  = st.st_uid;
	wi->entry.gid  = st.st_gid;
	wi->entry.file_size = st.st_size;

	/* if this is a file type we don't handle, treat as ignored */
	if (st.st_mode == 0)
		return GIT_SUCCESS;

410 411 412 413 414
	/* okay, we are far enough along to look up real ignore rule */
	error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored);
	if (error != GIT_SUCCESS)
		return GIT_SUCCESS;

415 416 417 418
	if (S_ISDIR(st.st_mode)) {
		if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) {
			/* create submodule entry */
			wi->entry.mode = S_IFGITLINK;
419 420
		} else {
			/* create directory entry that can be advanced into as needed */
421 422 423 424 425 426 427 428 429 430 431 432 433
			size_t pathlen = strlen(wi->entry.path);
			wi->entry.path[pathlen] = '/';
			wi->entry.path[pathlen + 1] = '\0';
			wi->entry.mode = S_IFDIR;
		}
	}

	return GIT_SUCCESS;
}

int git_iterator_for_workdir(git_repository *repo, git_iterator **iter)
{
	int error;
434
	workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator));
435 436 437
	if (!wi)
		return GIT_ENOMEM;

438 439 440 441 442 443 444 445 446 447 448
	wi->base.type    = GIT_ITERATOR_WORKDIR;
	wi->base.current = workdir_iterator__current;
	wi->base.at_end  = workdir_iterator__at_end;
	wi->base.advance = workdir_iterator__advance;
	wi->base.free    = workdir_iterator__free;
	wi->repo         = repo;

	error = git_buf_sets(&wi->path, git_repository_workdir(repo));
	if (error == GIT_SUCCESS)
		error = git_ignore__for_path(repo, "", &wi->ignores);
	if (error != GIT_SUCCESS) {
449 450 451 452 453 454
		git__free(wi);
		return error;
	}

	wi->root_len = wi->path.size;

455
	if ((error = workdir_iterator__expand_dir(wi)) < GIT_SUCCESS)
456 457 458 459 460 461 462
		git_iterator_free((git_iterator *)wi);
	else
		*iter = (git_iterator *)wi;

	return error;
}

463

464 465 466
int git_iterator_current_tree_entry(
	git_iterator *iter, const git_tree_entry **tree_entry)
{
467 468
	*tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL :
		tree_iterator__tree_entry((tree_iterator *)iter);
469 470 471 472 473
	return GIT_SUCCESS;
}

int git_iterator_current_is_ignored(git_iterator *iter)
{
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
	return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
		((workdir_iterator *)iter)->is_ignored;
}

int git_iterator_advance_into_directory(
	git_iterator *iter, const git_index_entry **entry)
{
	workdir_iterator *wi = (workdir_iterator *)iter;

	if (iter->type == GIT_ITERATOR_WORKDIR &&
		wi->entry.path && S_ISDIR(wi->entry.mode))
	{
		if (workdir_iterator__expand_dir(wi) < GIT_SUCCESS)
			/* if error loading or if empty, skip the directory. */
			return workdir_iterator__advance(iter, entry);
	}

	return entry ? git_iterator_current(iter, entry) : GIT_SUCCESS;
492
}