diff options
Diffstat (limited to 'src/path.c')
| -rw-r--r-- | src/path.c | 186 | 
1 files changed, 139 insertions, 47 deletions
diff --git a/src/path.c b/src/path.c index c4ab57136..857cdfb5d 100644 --- a/src/path.c +++ b/src/path.c @@ -18,6 +18,12 @@  #define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') +#if __APPLE__ +#include <iconv.h> +#endif +#define ICONV_REPO_ENCODING "UTF-8" +#define ICONV_PATH_ENCODING "UTF-8-MAC" +  #ifdef GIT_WIN32  static bool looks_like_network_computer_name(const char *path, int pos)  { @@ -724,14 +730,83 @@ int git_path_cmp(  	return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;  } +static bool path_has_non_ascii(const char *path, size_t pathlen) +{ +	const uint8_t *scan = (const uint8_t *)path, *end; + +	for (end = scan + pathlen; scan < end; ++scan) +		if (*scan & 0x80) +			return true; + +	return false; +} + +#ifdef __APPLE__ +static int path_iconv(iconv_t map, git_buf *out, char **in, size_t *inlen) +{ +	char *nfd = *in, *nfc; +	size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv; +	int retry = 1; + +	if (!path_has_non_ascii(*in, *inlen)) +		return 0; + +	while (1) { +		if (git_buf_grow(out, wantlen) < 0) +			return -1; + +		nfc    = out->ptr   + out->size; +		nfclen = out->asize - out->size; + +		rv = iconv(map, &nfd, &nfdlen, &nfc, &nfclen); + +		out->size = (nfc - out->ptr); + +		if (rv != (size_t)-1) +			break; + +		if (errno != E2BIG) +			return -1; + +		/* make space for 2x the remaining data to be converted +		 * (with per retry overhead to avoid infinite loops) +		 */ +		wantlen = out->size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + +		if (retry++ > 4) +			return -1; +	} + +	out->ptr[out->size] = '\0'; + +	*in    = out->ptr; +	*inlen = out->size; + +	return 0; +} +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif +  int git_path_direach(  	git_buf *path, +	uint32_t flags,  	int (*fn)(void *, git_buf *),  	void *arg)  { +	int error = 0;  	ssize_t wd_len;  	DIR *dir; -	struct dirent *de, *de_buf; +	path_dirent_data de_data; +	struct dirent *de, *de_buf = (struct dirent *)&de_data; +#ifdef __APPLE__ +	iconv_t nfd2nfc = (iconv_t)-1; +	git_buf nfc_path = GIT_BUF_INIT; +#endif  	if (git_path_to_dir(path) < 0)  		return -1; @@ -743,38 +818,46 @@ int git_path_direach(  		return -1;  	} -#if defined(__sun) || defined(__GNU__) -	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else -	de_buf = git__malloc(sizeof(struct dirent)); +#ifdef __APPLE__ +	if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) +		nfd2nfc = iconv_open(ICONV_REPO_ENCODING, ICONV_PATH_ENCODING);  #endif  	while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { -		int result; +		char *de_path = de->d_name; +		size_t de_len = strlen(de_path); -		if (git_path_is_dot_or_dotdot(de->d_name)) +		if (git_path_is_dot_or_dotdot(de_path))  			continue; -		if (git_buf_puts(path, de->d_name) < 0) { -			closedir(dir); -			git__free(de_buf); -			return -1; -		} +#if __APPLE__ +		if (nfd2nfc != (iconv_t)-1 && +			(error = path_iconv(nfd2nfc, &nfc_path, &de_path, &de_len)) < 0) +			break; +#endif -		result = fn(arg, path); +		if ((error = git_buf_put(path, de_path, de_len)) < 0) +			break; + +		error = fn(arg, path);  		git_buf_truncate(path, wd_len); /* restore path */ -		if (result) { -			closedir(dir); -			git__free(de_buf); -			return GIT_EUSER; +		if (error) { +			error = GIT_EUSER; +			break;  		}  	}  	closedir(dir); -	git__free(de_buf); -	return 0; + +#ifdef __APPLE__ +	if (nfd2nfc != (iconv_t)-1) +		iconv_close(nfd2nfc); +	git_buf_free(&nfc_path); +#endif + +	return error;  }  int git_path_dirload( @@ -786,22 +869,30 @@ int git_path_dirload(  {  	int error, need_slash;  	DIR *dir; -	struct dirent *de, *de_buf;  	size_t path_len; +	path_dirent_data de_data; +	struct dirent *de, *de_buf = (struct dirent *)&de_data; +#ifdef __APPLE__ +	iconv_t nfd2nfc = (iconv_t)-1; +	git_buf nfc_path = GIT_BUF_INIT; +#endif + +	assert(path && contents); -	assert(path != NULL && contents != NULL);  	path_len = strlen(path); -	assert(path_len > 0 && path_len >= prefix_len); +	if (!path_len || path_len < prefix_len) { +		giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path); +		return -1; +	}  	if ((dir = opendir(path)) == NULL) {  		giterr_set(GITERR_OS, "Failed to open directory '%s'", path);  		return -1;  	} -#if defined(__sun) || defined(__GNU__) -	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else -	de_buf = git__malloc(sizeof(struct dirent)); +#ifdef __APPLE__ +	if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) +		nfd2nfc = iconv_open(ICONV_REPO_ENCODING, ICONV_PATH_ENCODING);  #endif  	path += prefix_len; @@ -809,40 +900,41 @@ int git_path_dirload(  	need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;  	while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) { -		char *entry_path; -		size_t entry_len; +		char *entry_path, *de_path = de->d_name; +		size_t alloc_size, de_len = strlen(de_path); -		if (git_path_is_dot_or_dotdot(de->d_name)) +		if (git_path_is_dot_or_dotdot(de_path))  			continue; -		entry_len = strlen(de->d_name); +#if __APPLE__ +		if (nfd2nfc != (iconv_t)-1 && +			(error = path_iconv(nfd2nfc, &nfc_path, &de_path, &de_len)) < 0) +			break; +#endif -		/* if we read decomposed unicode and precompose flag is set, -		 * then precompose it now so app code sees it as precomposed -		 */ -		if ((flags & GIT_PATH_DIRLOAD_PRECOMPOSE_UNICODE) != 0) { +		alloc_size = path_len + need_slash + de_len + 1 + alloc_extra; +		if ((entry_path = git__calloc(alloc_size, 1)) == NULL) { +			error = -1; +			break;  		} -		entry_path = git__malloc( -			path_len + need_slash + entry_len + 1 + alloc_extra); -		GITERR_CHECK_ALLOC(entry_path); -  		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'; +		memcpy(&entry_path[path_len + need_slash], de_path, de_len); -		if (git_vector_insert(contents, entry_path) < 0) { -			closedir(dir); -			git__free(de_buf); -			return -1; -		} +		if ((error = git_vector_insert(contents, entry_path)) < 0) +			break;  	}  	closedir(dir); -	git__free(de_buf); + +#ifdef __APPLE__ +	if (nfd2nfc != (iconv_t)-1) +		iconv_close(nfd2nfc); +	git_buf_free(&nfc_path); +#endif  	if (error != 0)  		giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); @@ -888,7 +980,7 @@ int git_path_dirload_with_stat(  		return error;  	} -	strncomp = (flags & GIT_PATH_DIRLOAD_IGNORE_CASE) != 0 ? +	strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?  		git__strncasecmp : git__strncmp;  	/* stat struct at start of git_path_with_stat, so shift path text */  | 
