mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-04 19:39:48 +00:00
Add path utilities to resolve relative paths
This makes it easy to take a buffer containing a path with relative references (i.e. .. or . path segments) and resolve all of those into a clean path. This can be applied to URLs as well as file paths which can be useful. As part of this, I made the drive-letter detection apply on all platforms, not just windows. If you give a path that looks like "c:/..." on any platform, it seems like we might as well detect that as a rooted path. I suppose if you create a directory named "x:" on another platform and want to use that as the beginning of a relative path under the root directory of your repo, this could cause a problem, but then it seems like you're asking for trouble.
This commit is contained in:
parent
039fc40679
commit
b0fe112922
69
src/path.c
69
src/path.c
@ -17,9 +17,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
#ifdef GIT_WIN32
|
|
||||||
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
|
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Based on the Android implementation, BSD licensed.
|
* Based on the Android implementation, BSD licensed.
|
||||||
@ -172,11 +170,11 @@ int git_path_root(const char *path)
|
|||||||
{
|
{
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
#ifdef GIT_WIN32
|
|
||||||
/* Does the root of the path look like a windows drive ? */
|
/* Does the root of the path look like a windows drive ? */
|
||||||
if (LOOKS_LIKE_DRIVE_PREFIX(path))
|
if (LOOKS_LIKE_DRIVE_PREFIX(path))
|
||||||
offset += 2;
|
offset += 2;
|
||||||
|
|
||||||
|
#ifdef GIT_WIN32
|
||||||
/* Are we dealing with a windows network path? */
|
/* Are we dealing with a windows network path? */
|
||||||
else if ((path[0] == '/' && path[1] == '/') ||
|
else if ((path[0] == '/' && path[1] == '/') ||
|
||||||
(path[0] == '\\' && path[1] == '\\'))
|
(path[0] == '\\' && path[1] == '\\'))
|
||||||
@ -464,6 +462,71 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
int git_path_cmp(
|
int git_path_cmp(
|
||||||
const char *name1, size_t len1, int isdir1,
|
const char *name1, size_t len1, int isdir1,
|
||||||
const char *name2, size_t len2, int isdir2)
|
const char *name2, size_t len2, int isdir2)
|
||||||
|
23
src/path.h
23
src/path.h
@ -185,6 +185,29 @@ extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char
|
|||||||
*/
|
*/
|
||||||
extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
|
extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve relative references within a path.
|
||||||
|
*
|
||||||
|
* This eliminates "./" and "../" relative references inside a path,
|
||||||
|
* as well as condensing multiple slashes into single ones. It will
|
||||||
|
* not touch the path before the "ceiling" length.
|
||||||
|
*
|
||||||
|
* Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL
|
||||||
|
* prefix and not touch that part of the path.
|
||||||
|
*/
|
||||||
|
extern int git_path_resolve_relative(git_buf *path, size_t ceiling);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a relative path to base path.
|
||||||
|
*
|
||||||
|
* Note that the base path could be a filename or a URL and this
|
||||||
|
* should still work. The relative path is walked segment by segment
|
||||||
|
* with three rules: series of slashes will be condensed to a single
|
||||||
|
* slash, "." will be eaten with no change, and ".." will remove a
|
||||||
|
* segment from the base path.
|
||||||
|
*/
|
||||||
|
extern int git_path_apply_relative(git_buf *target, const char *relpath);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walk each directory entry, except '.' and '..', calling fn(state).
|
* Walk each directory entry, except '.' and '..', calling fn(state).
|
||||||
*
|
*
|
||||||
|
@ -418,3 +418,54 @@ void test_core_path__13_cannot_prettify_a_non_existing_file(void)
|
|||||||
|
|
||||||
git_buf_free(&p);
|
git_buf_free(&p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_core_path__14_apply_relative(void)
|
||||||
|
{
|
||||||
|
git_buf p = GIT_BUF_INIT;
|
||||||
|
|
||||||
|
cl_git_pass(git_buf_sets(&p, "/this/is/a/base"));
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../test"));
|
||||||
|
cl_assert_equal_s("/this/is/a/test", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../the/./end"));
|
||||||
|
cl_assert_equal_s("/this/is/the/end", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "./of/this/../the/string"));
|
||||||
|
cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../../../../.."));
|
||||||
|
cl_assert_equal_s("/this/", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../../../../"));
|
||||||
|
cl_assert_equal_s("/", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../../../.."));
|
||||||
|
cl_assert_equal_s("/", p.ptr);
|
||||||
|
|
||||||
|
|
||||||
|
cl_git_pass(git_buf_sets(&p, "d:/another/test"));
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../../../.."));
|
||||||
|
cl_assert_equal_s("d:/", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/."));
|
||||||
|
cl_assert_equal_s("d:/from/here/and/back/", p.ptr);
|
||||||
|
|
||||||
|
|
||||||
|
cl_git_pass(git_buf_sets(&p, "https://my.url.com/test.git"));
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../another.git"));
|
||||||
|
cl_assert_equal_s("https://my.url.com/another.git", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../full/path/url.patch"));
|
||||||
|
cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, ".."));
|
||||||
|
cl_assert_equal_s("https://my.url.com/full/path/", p.ptr);
|
||||||
|
|
||||||
|
cl_git_pass(git_path_apply_relative(&p, "../../../../../"));
|
||||||
|
cl_assert_equal_s("https://", p.ptr);
|
||||||
|
|
||||||
|
git_buf_free(&p);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user