Original design of relative_path() is simple, just strip the prefix
(*base) from the abosolute path (*abs). In most cases, we need a real
relative path, such as: ../foo, ../../bar. That's why there is another
reimplementation (path_relative()) in quote.c.
Refactor relative_path() in path.c to return real relative path, so
that user can reuse this function without reimplement his/her own.
I will use this method for interactive git-clean later. Some of the
implementations are borrowed from path_relative() in quote.c.
Different results for relative_path() before and after this refactor:
base path abs path relative (orignal) relative (refactor)
========= ======== ================== ===================
/a/b /a/b/c/ c/ c/
//a///b/ /a/b//c/ c/ c/
/a/b /a/b/ . ./
/a/b/ /a /a ../
/a/b/ / / ../../
/a/b/ /a/c /a/c ../c
Signed-off-by: Jiang Xin <[email protected]>
---
path.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 74 insertions(+), 20 deletions(-)
diff --git a/path.c b/path.c
index 04ff..4dafa8 100644
--- a/path.c
+++ b/path.c
@@ -444,38 +444,92 @@ int adjust_shared_perm(const char *path)
const char *relative_path(const char *abs, const char *base)
{
static char buf[PATH_MAX + 1];
- int i = 0, j = 0;
+ int abs_off, base_off, i, j;
+ int abs_len, base_len;
if (!base || !base[0])
return abs;
- while (base[i]) {
+
+ abs_len = strlen(abs);
+ base_len = strlen(base);
+
+ abs_off = 0;
+ base_off = 0;
+ i = 0;
+ j = 0;
+ while (i < base_len && j < abs_len && base[i] == abs[j]) {
if (is_dir_sep(base[i])) {
- if (!is_dir_sep(abs[j]))
- return abs;
while (is_dir_sep(base[i]))
i++;
while (is_dir_sep(abs[j]))
j++;
- continue;
- } else if (abs[j] != base[i]) {
+ base_off = i;
+ abs_off = j;
+ } else {
+ i++;
+ j++;
+ }
+ }
+ if (
+ /* base seems like prefix of abs */
+ i >= base_len &&
+ /*
+ * but "/foo" is not a prefix of "/foobar"
+ * (i.e. base not end with '/')
+ */
+ base_off < base_len) {
+ if (j >= abs_len) {
+ /* abs="/a/b", base="/a/b" */
+ abs_off = abs_len;
+ } else if (is_dir_sep(abs[j])) {
+ /* abs="/a/b/c", base="/a/b" */
+ while (is_dir_sep(abs[j]))
+ j++;
+ abs_off = j;
+ } else {
+ /* abs="/a/bbb/c", base="/a/b" */
+ i = base_off;
+ }
+ } else if (
+ /* abs is short than base (prefix of base) */
+ j >= abs_len &&
+ /* abs not end with '/' */
+ abs_off < abs_len) {
+ if (is_dir_sep(base[i])) {
+ /* abs="/a/b", base="/a/b/c/" */
+ while (is_dir_sep(base[i]))
+ i++;
+ abs_off = abs_len;
+ }
+ }
+ abs += abs_off;
+ abs_len -= abs_off;
+
+ /* base is prefix of abs */
+ if (i >= base_len) {
+ if (*abs == '\0') {
+ strcpy(buf, "./");
+ return buf;
+ } else {
return abs;
}
+ }
+
+ buf[0] = '\0';
+ while (i < base_len) {
+ if (is_dir_sep(base[i])) {
+ strcat(buf, "../");
+ while (is_dir_sep(base[i]))
+ i++;
+ continue;
+ }
i++;
- j++;
}
- if (
- /* "/foo" is a prefix of "/foo" */
- abs[j] &&
- /* "/foo" is not a prefix of "/foobar" */
- !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
- )
- return abs;
- while (is_dir_sep(abs[j]))
- j++;
- if (!abs[j])
- strcpy(buf, ".");
- else
- strcpy(buf, abs + j);
+ if (!is_dir_sep(base[base_len - 1]))
+ strcat(buf, "../");
+
+ strcat(buf, abs);
+
return buf;
}
--
1.8.3.rc1.404.ga32c147
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html