From 0ee9f31c3b11116ab5806ab80d03b1d37197d6ce Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 20 Aug 2014 10:23:39 -0400 Subject: Introduce git_path_make_relative --- src/path.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/path.h | 11 +++++++++++ tests/path/core.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 tests/path/core.c diff --git a/src/path.c b/src/path.c index 77f8d8858..d29b992fe 100644 --- a/src/path.c +++ b/src/path.c @@ -750,6 +750,61 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +int git_path_make_relative(git_buf *path, const char *parent) +{ + const char *p, *q, *p_dirsep, *q_dirsep; + size_t plen = path->size, newlen, depth = 1, i; + + for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') { + p_dirsep = p; + q_dirsep = q; + } + else if (*p != *q) + break; + } + + /* need at least 1 common path segment */ + if ((p_dirsep == path->ptr || q_dirsep == parent) && + (*p_dirsep != '/' || *q_dirsep != '/')) { + giterr_set(GITERR_INVALID, + "%s is not a parent of %s", parent, path->ptr); + return GIT_ENOTFOUND; + } + + if (*p == '/' && !*q) + p++; + else if (!*p && *q == '/') + q++; + else if (!*p && !*q) + return git_buf_clear(path), 0; + else { + p = p_dirsep + 1; + q = q_dirsep + 1; + } + + plen -= (p - path->ptr); + + if (!*q) + return git_buf_set(path, p, plen); + + for (; (q = strchr(q, '/')) && *(q + 1); q++) + depth++; + + newlen = (depth * 3) + plen; + + if (git_buf_try_grow(path, newlen + 1, 1, 0) < 0) + return -1; + + memmove(path->ptr + (depth * 3), p, plen + 1); + + for (i = 0; i < depth; i++) + memcpy(path->ptr + (i * 3), "../", 3); + + path->size = newlen; + return 0; +} + bool git_path_has_non_ascii(const char *path, size_t pathlen) { const uint8_t *scan = (const uint8_t *)path, *end; diff --git a/src/path.h b/src/path.h index 46d6efe93..d0a9de707 100644 --- a/src/path.h +++ b/src/path.h @@ -196,6 +196,17 @@ extern bool git_path_contains(git_buf *dir, const char *item); */ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); +/** + * Make the path relative to the given parent path. + * + * @param path The path to make relative + * @param parent The parent path to make path relative to + * @return 0 if path was made relative, GIT_ENOTFOUND + * if there was not common root between the paths, + * or <0. + */ +extern int git_path_make_relative(git_buf *path, const char *parent); + /** * Check if the given path contains the given file. * diff --git a/tests/path/core.c b/tests/path/core.c new file mode 100644 index 000000000..be63e309b --- /dev/null +++ b/tests/path/core.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" +#include "path.h" + +static void test_make_relative( + const char *expected_path, + const char *path, + const char *parent, + int expected_status) +{ + git_buf buf = GIT_BUF_INIT; + git_buf_puts(&buf, path); + cl_assert_equal_i(expected_status, git_path_make_relative(&buf, parent)); + cl_assert_equal_s(expected_path, buf.ptr); + git_buf_free(&buf); +} + +void test_path_core__make_relative(void) +{ + git_buf buf = GIT_BUF_INIT; + + test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0); + test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0); + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + + test_make_relative("", "/path/to", "/path/to", 0); + test_make_relative("", "/path/to", "/path/to/", 0); + + test_make_relative("../", "/path/to", "/path/to/foo", 0); + + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0); + test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0); + + test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0); + test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0); + + test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0); + + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0); + + test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0); + + test_make_relative("../foo", "/foo", "/bar", 0); + test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0); + test_make_relative("../foo", "path/to/foo", "path/to/bar", 0); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND); + test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND); + test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); + test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); +} -- cgit v1.2.1