path.c 16.9 KB
Newer Older
Vicent Marti committed
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
Vicent Marti committed
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
Vicent Marti committed
7
#include "common.h"
8 9
#include "path.h"
#include "posix.h"
10 11
#ifdef GIT_WIN32
#include "win32/dir.h"
12
#include "win32/posix.h"
13 14 15
#else
#include <dirent.h>
#endif
Vicent Marti committed
16 17 18 19
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>

20 21
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')

Vicent Marti committed
22 23 24 25
/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
26
int git_path_basename_r(git_buf *buffer, const char *path)
Vicent Marti committed
27 28 29 30 31 32
{
	const char *endp, *startp;
	int len, result;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
Vicent Marti committed
33 34
		startp = ".";
		len		= 1;
Vicent Marti committed
35 36 37 38 39 40 41 42 43 44 45
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* All slashes becomes "/" */
	if (endp == path && *endp == '/') {
		startp = "/";
Vicent Marti committed
46
		len	= 1;
Vicent Marti committed
47 48 49 50 51 52 53 54
		goto Exit;
	}

	/* Find the start of the base */
	startp = endp;
	while (startp > path && *(startp - 1) != '/')
		startp--;

55 56
	/* Cast is safe because max path < max int */
	len = (int)(endp - startp + 1);
Vicent Marti committed
57 58 59 60

Exit:
	result = len;

61 62
	if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
		return -1;
63

Vicent Marti committed
64 65 66 67 68 69 70
	return result;
}

/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
71
int git_path_dirname_r(git_buf *buffer, const char *path)
Vicent Marti committed
72
{
Vicent Marti committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	const char *endp;
	int result, len;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
		path = ".";
		len = 1;
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* Find the start of the dir */
	while (endp > path && *endp != '/')
		endp--;

	/* Either the dir is "/" or there are no slashes */
	if (endp == path) {
		path = (*endp == '/') ? "/" : ".";
		len = 1;
		goto Exit;
	}

	do {
		endp--;
	} while (endp > path && *endp == '/');

103 104
	/* Cast is safe because max path < max int */
	len = (int)(endp - path + 1);
Vicent Marti committed
105

Jerome Lambourg committed
106
#ifdef GIT_WIN32
Vicent Marti committed
107 108
	/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
		'C:/' here */
Jerome Lambourg committed
109

110
	if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
Vicent Marti committed
111 112 113
		len = 3;
		goto Exit;
	}
Jerome Lambourg committed
114 115
#endif

Vicent Marti committed
116
Exit:
Vicent Marti committed
117 118
	result = len;

119 120
	if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
		return -1;
Vicent Marti committed
121 122

	return result;
Vicent Marti committed
123 124 125 126 127
}


char *git_path_dirname(const char *path)
{
128 129
	git_buf buf = GIT_BUF_INIT;
	char *dirname;
Vicent Marti committed
130

131 132 133
	git_path_dirname_r(&buf, path);
	dirname = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
134

135
	return dirname;
Vicent Marti committed
136 137 138 139
}

char *git_path_basename(const char *path)
{
140 141
	git_buf buf = GIT_BUF_INIT;
	char *basename;
Vicent Marti committed
142

143 144 145
	git_path_basename_r(&buf, path);
	basename = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
146

147
	return basename;
Vicent Marti committed
148 149
}

150 151 152 153 154 155 156 157 158 159 160 161 162 163
size_t git_path_basename_offset(git_buf *buffer)
{
	ssize_t slash;

	if (!buffer || buffer->size <= 0)
		return 0;

	slash = git_buf_rfind_next(buffer, '/');

	if (slash >= 0 && buffer->ptr[slash] == '/')
		return (size_t)(slash + 1);

	return 0;
}
Vicent Marti committed
164 165 166 167

const char *git_path_topdir(const char *path)
{
	size_t len;
168
	ssize_t i;
Vicent Marti committed
169 170 171 172 173 174 175

	assert(path);
	len = strlen(path);

	if (!len || path[len - 1] != '/')
		return NULL;

176
	for (i = (ssize_t)len - 2; i >= 0; --i)
Vicent Marti committed
177 178 179 180 181 182
		if (path[i] == '/')
			break;

	return &path[i + 1];
}

183 184 185 186 187
int git_path_root(const char *path)
{
	int offset = 0;

	/* Does the root of the path look like a windows drive ? */
188
	if (LOOKS_LIKE_DRIVE_PREFIX(path))
189
		offset += 2;
190

191
#ifdef GIT_WIN32
192 193 194 195
	/* Are we dealing with a windows network path? */
	else if ((path[0] == '/' && path[1] == '/') ||
		(path[0] == '\\' && path[1] == '\\'))
	{
196
		offset += 2;
197

198
		/* Skip the computer name segment */
199
		while (path[offset] && path[offset] != '/' && path[offset] != '\\')
200 201
			offset++;
	}
202 203
#endif

204
	if (path[offset] == '/' || path[offset] == '\\')
205 206
		return offset;

207
	return -1;	/* Not a real error - signals that path is not rooted */
208 209
}

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
int git_path_join_unrooted(
	git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
{
	int error, root;

	assert(path && path_out);

	root = git_path_root(path);

	if (base != NULL && root < 0) {
		error = git_buf_joinpath(path_out, base, path);

		if (root_at)
			*root_at = (ssize_t)strlen(base);
	}
	else {
		error = git_buf_sets(path_out, path);

		if (root_at)
			*root_at = (root < 0) ? 0 : (ssize_t)root;
	}

	return error;
}

235
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
236
{
237
	char buf[GIT_PATH_MAX];
238

239
	assert(path && path_out);
240 241 242

	/* construct path if needed */
	if (base != NULL && git_path_root(path) < 0) {
243 244
		if (git_buf_joinpath(path_out, base, path) < 0)
			return -1;
245 246 247
		path = path_out->ptr;
	}

248
	if (p_realpath(path, buf) == NULL) {
249 250
		/* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
		int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
251
		giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
252

253
		git_buf_clear(path_out);
254

255
		return error;
256
	}
257

258
	return git_buf_sets(path_out, buf);
259 260
}

261
int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
262
{
263
	int error = git_path_prettify(path_out, path, base);
264
	return (error < 0) ? error : git_path_to_dir(path_out);
265
}
266

267 268 269
int git_path_to_dir(git_buf *path)
{
	if (path->asize > 0 &&
nulltoken committed
270 271
		git_buf_len(path) > 0 &&
		path->ptr[git_buf_len(path) - 1] != '/')
272
		git_buf_putc(path, '/');
273

274
	return git_buf_oom(path) ? -1 : 0;
275
}
276 277 278 279 280 281 282 283 284 285 286

void git_path_string_to_dir(char* path, size_t size)
{
	size_t end = strlen(path);

	if (end && path[end - 1] != '/' && end < size) {
		path[end] = '/';
		path[end + 1] = '\0';
	}
}

287 288
int git__percent_decode(git_buf *decoded_out, const char *input)
{
289
	int len, hi, lo, i;
290 291
	assert(decoded_out && input);

292
	len = (int)strlen(input);
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
	git_buf_clear(decoded_out);

	for(i = 0; i < len; i++)
	{
		char c = input[i];

		if (c != '%')
			goto append;

		if (i >= len - 2)
			goto append;

		hi = git__fromhex(input[i + 1]);
		lo = git__fromhex(input[i + 2]);

		if (hi < 0 || lo < 0)
			goto append;

		c = (char)(hi << 4 | lo);
		i += 2;

append:
315 316
		if (git_buf_putc(decoded_out, c) < 0)
			return -1;
317 318
	}

319 320 321 322 323 324 325
	return 0;
}

static int error_invalid_local_file_uri(const char *uri)
{
	giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri);
	return -1;
326
}
nulltoken committed
327 328 329

int git_path_fromurl(git_buf *local_path_out, const char *file_url)
{
330
	int offset = 0, len;
nulltoken committed
331 332 333 334

	assert(local_path_out && file_url);

	if (git__prefixcmp(file_url, "file://") != 0)
335
		return error_invalid_local_file_uri(file_url);
nulltoken committed
336 337

	offset += 7;
338
	len = (int)strlen(file_url);
nulltoken committed
339 340 341 342 343 344

	if (offset < len && file_url[offset] == '/')
		offset++;
	else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0)
		offset += 10;
	else
345
		return error_invalid_local_file_uri(file_url);
nulltoken committed
346 347

	if (offset >= len || file_url[offset] == '/')
348
		return error_invalid_local_file_uri(file_url);
nulltoken committed
349 350 351 352 353 354 355

#ifndef _MSC_VER
	offset--;	/* A *nix absolute path starts with a forward slash */
#endif

	git_buf_clear(local_path_out);

356
	return git__percent_decode(local_path_out, file_url + offset);
nulltoken committed
357
}
358 359 360 361 362 363 364

int git_path_walk_up(
	git_buf *path,
	const char *ceiling,
	int (*cb)(void *data, git_buf *),
	void *data)
{
365
	int error = 0;
366 367 368 369 370 371 372
	git_buf iter;
	ssize_t stop = 0, scan;
	char oldc = '\0';

	assert(path && cb);

	if (ceiling != NULL) {
373
		if (git__prefixcmp(path->ptr, ceiling) == 0)
374 375
			stop = (ssize_t)strlen(ceiling);
		else
nulltoken committed
376
			stop = git_buf_len(path);
377
	}
nulltoken committed
378
	scan = git_buf_len(path);
379 380

	iter.ptr = path->ptr;
nulltoken committed
381
	iter.size = git_buf_len(path);
382
	iter.asize = path->asize;
383 384

	while (scan >= stop) {
385
		error = cb(data, &iter);
386
		iter.ptr[scan] = oldc;
387 388
		if (error < 0)
			break;
389 390 391 392 393 394 395 396 397
		scan = git_buf_rfind_next(&iter, '/');
		if (scan >= 0) {
			scan++;
			oldc = iter.ptr[scan];
			iter.size = scan;
			iter.ptr[scan] = '\0';
		}
	}

398 399
	if (scan >= 0)
		iter.ptr[scan] = oldc;
400 401 402

	return error;
}
403

404
bool git_path_exists(const char *path)
405 406
{
	assert(path);
407
	return p_access(path, F_OK) == 0;
408 409
}

410
bool git_path_isdir(const char *path)
411 412
{
	struct stat st;
413 414
	if (p_stat(path, &st) < 0)
		return false;
415

416
	return S_ISDIR(st.st_mode) != 0;
417 418
}

419
bool git_path_isfile(const char *path)
420 421 422 423
{
	struct stat st;

	assert(path);
424 425
	if (p_stat(path, &st) < 0)
		return false;
426

427
	return S_ISREG(st.st_mode) != 0;
428 429
}

Ben Straub committed
430 431 432 433
#ifdef GIT_WIN32

bool git_path_is_empty_dir(const char *path)
{
434
	git_buf pathbuf = GIT_BUF_INIT;
Ben Straub committed
435
	HANDLE hFind = INVALID_HANDLE_VALUE;
436
	wchar_t wbuf[GIT_WIN_PATH];
Ben Straub committed
437 438 439 440 441
	WIN32_FIND_DATAW ffd;
	bool retval = true;

	if (!git_path_isdir(path)) return false;

442
	git_buf_printf(&pathbuf, "%s\\*", path);
443
	git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf));
444

Ben Straub committed
445
	hFind = FindFirstFileW(wbuf, &ffd);
446 447 448
	if (INVALID_HANDLE_VALUE == hFind) {
		giterr_set(GITERR_OS, "Couldn't open '%s'", path);
		return false;
Ben Straub committed
449
	}
450 451

	do {
452
		if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
453 454 455 456 457 458
			retval = false;
		}
	} while (FindNextFileW(hFind, &ffd) != 0);

	FindClose(hFind);
	git_buf_free(&pathbuf);
Ben Straub committed
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
	return retval;
}

#else

bool git_path_is_empty_dir(const char *path)
{
	DIR *dir = NULL;
	struct dirent *e;
	bool retval = true;

	if (!git_path_isdir(path)) return false;

	dir = opendir(path);
	if (!dir) {
		giterr_set(GITERR_OS, "Couldn't open '%s'", path);
		return false;
	}

	while ((e = readdir(dir)) != NULL) {
		if (!git_path_is_dot_or_dotdot(e->d_name)) {
			giterr_set(GITERR_INVALID,
						  "'%s' exists and is not an empty directory", path);
			retval = false;
			break;
		}
	}
	closedir(dir);

	return retval;
}
#endif

492 493 494
int git_path_lstat(const char *path, struct stat *st)
{
	int err = 0;
495

496 497 498 499 500 501
	if (p_lstat(path, st) < 0) {
		err = (errno == ENOENT) ? GIT_ENOTFOUND : -1;
		giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
	}

	return err;
502 503
}

504
static bool _check_dir_contents(
505 506
	git_buf *dir,
	const char *sub,
507
	bool (*predicate)(const char *))
508
{
509
	bool result;
nulltoken committed
510
	size_t dir_size = git_buf_len(dir);
511 512
	size_t sub_size = strlen(sub);

513
	/* leave base valid even if we could not make space for subdir */
514
	if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0)
515 516 517
		return false;

	/* save excursion */
518 519
	git_buf_joinpath(dir, dir->ptr, sub);

520
	result = predicate(dir->ptr);
521

522 523
	/* restore path */
	git_buf_truncate(dir, dir_size);
524
	return result;
525 526
}

527
bool git_path_contains(git_buf *dir, const char *item)
528
{
529
	return _check_dir_contents(dir, item, &git_path_exists);
530 531
}

532
bool git_path_contains_dir(git_buf *base, const char *subdir)
533
{
534
	return _check_dir_contents(base, subdir, &git_path_isdir);
535 536
}

537
bool git_path_contains_file(git_buf *base, const char *file)
538
{
539
	return _check_dir_contents(base, file, &git_path_isfile);
540 541 542 543
}

int git_path_find_dir(git_buf *dir, const char *path, const char *base)
{
544
	int error = git_path_join_unrooted(dir, path, base, NULL);
545

546
	if (!error) {
547 548 549 550 551 552
		char buf[GIT_PATH_MAX];
		if (p_realpath(dir->ptr, buf) != NULL)
			error = git_buf_sets(dir, buf);
	}

	/* call dirname if this is not a directory */
553 554
	if (!error && git_path_isdir(dir->ptr) == false)
		error = git_path_dirname_r(dir, dir->ptr);
555

556
	if (!error)
557 558 559 560 561
		error = git_path_to_dir(dir);

	return error;
}

562 563 564 565 566 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 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
int git_path_resolve_relative(git_buf *path, size_t ceiling)
{
	char *base, *to, *from, *next;
	size_t len;

	if (!path || git_buf_oom(path))
		return -1;

	if (ceiling > path->size)
		ceiling = path->size;

	/* recognize drive prefixes, etc. that should not be backed over */
	if (ceiling == 0)
		ceiling = git_path_root(path->ptr) + 1;

	/* recognize URL prefixes that should not be backed over */
	if (ceiling == 0) {
		for (next = path->ptr; *next && git__isalpha(*next); ++next);
		if (next[0] == ':' && next[1] == '/' && next[2] == '/')
			ceiling = (next + 3) - path->ptr;
	}

	base = to = from = path->ptr + ceiling;

	while (*from) {
		for (next = from; *next && *next != '/'; ++next);

		len = next - from;

		if (len == 1 && from[0] == '.')
			/* do nothing with singleton dot */;

		else if (len == 2 && from[0] == '.' && from[1] == '.') {
			while (to > base && to[-1] == '/') to--;
			while (to > base && to[-1] != '/') to--;
		}

		else {
			if (*next == '/')
				len++;

			if (to != from)
				memmove(to, from, len);

			to += len;
		}

		from += len;

		while (*from == '/') from++;
	}

	*to = '\0';

	path->size = to - path->ptr;

	return 0;
}

int git_path_apply_relative(git_buf *target, const char *relpath)
{
	git_buf_joinpath(target, git_buf_cstr(target), relpath);
	return git_path_resolve_relative(target, 0);
}

627 628 629
int git_path_cmp(
	const char *name1, size_t len1, int isdir1,
	const char *name2, size_t len2, int isdir2)
630
{
631
	unsigned char c1, c2;
632
	size_t len = len1 < len2 ? len1 : len2;
633 634 635 636 637
	int cmp;

	cmp = memcmp(name1, name2, len);
	if (cmp)
		return cmp;
638 639 640 641 642 643 644 645 646 647 648

	c1 = name1[len];
	c2 = name2[len];

	if (c1 == '\0' && isdir1)
		c1 = '/';

	if (c2 == '\0' && isdir2)
		c2 = '/';

	return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
649 650 651 652 653 654 655 656 657
}

int git_path_direach(
	git_buf *path,
	int (*fn)(void *, git_buf *),
	void *arg)
{
	ssize_t wd_len;
	DIR *dir;
658
	struct dirent *de, *de_buf;
659

660
	if (git_path_to_dir(path) < 0)
661
		return -1;
662

nulltoken committed
663
	wd_len = git_buf_len(path);
664

665 666
	if ((dir = opendir(path->ptr)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
667 668
		return -1;
	}
669

670
#if defined(__sun) || defined(__GNU__)
671 672 673 674 675 676
	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
	de_buf = git__malloc(sizeof(struct dirent));
#endif

	while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
677 678
		int result;

679
		if (git_path_is_dot_or_dotdot(de->d_name))
680 681
			continue;

682 683 684
		if (git_buf_puts(path, de->d_name) < 0) {
			closedir(dir);
			git__free(de_buf);
685
			return -1;
686
		}
687 688 689 690 691

		result = fn(arg, path);

		git_buf_truncate(path, wd_len); /* restore path */

692
		if (result < 0) {
693
			closedir(dir);
694
			git__free(de_buf);
695
			return -1;
696 697 698 699
		}
	}

	closedir(dir);
700
	git__free(de_buf);
701
	return 0;
702
}
703 704 705 706 707 708 709 710 711

int git_path_dirload(
	const char *path,
	size_t prefix_len,
	size_t alloc_extra,
	git_vector *contents)
{
	int error, need_slash;
	DIR *dir;
712
	struct dirent *de, *de_buf;
713 714 715 716 717 718
	size_t path_len;

	assert(path != NULL && contents != NULL);
	path_len = strlen(path);
	assert(path_len > 0 && path_len >= prefix_len);

719 720 721 722
	if ((dir = opendir(path)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
		return -1;
	}
723

724
#if defined(__sun) || defined(__GNU__)
725 726 727 728 729
	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
	de_buf = git__malloc(sizeof(struct dirent));
#endif

730 731 732 733
	path += prefix_len;
	path_len -= prefix_len;
	need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;

734
	while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
735 736 737
		char *entry_path;
		size_t entry_len;

738
		if (git_path_is_dot_or_dotdot(de->d_name))
739 740 741 742 743 744
			continue;

		entry_len = strlen(de->d_name);

		entry_path = git__malloc(
			path_len + need_slash + entry_len + 1 + alloc_extra);
745
		GITERR_CHECK_ALLOC(entry_path);
746 747 748 749 750 751 752 753

		if (path_len)
			memcpy(entry_path, path, path_len);
		if (need_slash)
			entry_path[path_len] = '/';
		memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len);
		entry_path[path_len + need_slash + entry_len] = '\0';

754 755 756
		if (git_vector_insert(contents, entry_path) < 0) {
			closedir(dir);
			git__free(de_buf);
757
			return -1;
758
		}
759 760 761
	}

	closedir(dir);
762
	git__free(de_buf);
763

764 765
	if (error != 0)
		giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
766

767
	return error;
768 769
}

770 771 772
int git_path_with_stat_cmp(const void *a, const void *b)
{
	const git_path_with_stat *psa = a, *psb = b;
773 774 775 776 777 778 779
	return strcmp(psa->path, psb->path);
}

int git_path_with_stat_cmp_icase(const void *a, const void *b)
{
	const git_path_with_stat *psa = a, *psb = b;
	return strcasecmp(psa->path, psb->path);
780 781 782 783 784
}

int git_path_dirload_with_stat(
	const char *path,
	size_t prefix_len,
785 786 787
	bool ignore_case,
	const char *start_stat,
	const char *end_stat,
788 789 790 791 792 793
	git_vector *contents)
{
	int error;
	unsigned int i;
	git_path_with_stat *ps;
	git_buf full = GIT_BUF_INIT;
794 795 796
	int (*strncomp)(const char *a, const char *b, size_t sz);
	size_t start_len = start_stat ? strlen(start_stat) : 0;
	size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
797

798 799
	if (git_buf_set(&full, path, prefix_len) < 0)
		return -1;
800

801 802 803
	error = git_path_dirload(
		path, prefix_len, sizeof(git_path_with_stat) + 1, contents);
	if (error < 0) {
804 805 806 807
		git_buf_free(&full);
		return error;
	}

808 809 810
	strncomp = ignore_case ? git__strncasecmp : git__strncmp;

	/* stat struct at start of git_path_with_stat, so shift path text */
811 812 813 814
	git_vector_foreach(contents, i, ps) {
		size_t path_len = strlen((char *)ps);
		memmove(ps->path, ps, path_len + 1);
		ps->path_len = path_len;
815 816 817 818 819 820 821 822 823 824
	}

	git_vector_foreach(contents, i, ps) {
		/* skip if before start_stat or after end_stat */
		cmp_len = min(start_len, ps->path_len);
		if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
			continue;
		cmp_len = min(end_len, ps->path_len);
		if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
			continue;
825

826 827
		if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
			(error = git_path_lstat(full.ptr, &ps->st)) < 0)
828 829
			break;

830 831 832
		git_buf_truncate(&full, prefix_len);

		if (S_ISDIR(ps->st.st_mode)) {
833 834
			ps->path[ps->path_len++] = '/';
			ps->path[ps->path_len] = '\0';
835 836 837
		}
	}

838 839 840
	/* sort now that directory suffix is added */
	git_vector_sort(contents);

841 842
	git_buf_free(&full);

843 844
	return error;
}