Index: subversion/include/svn_path.h
===================================================================
--- subversion/include/svn_path.h (revision 21156)
+++ subversion/include/svn_path.h (working copy)
@@ -161,6 +161,7 @@
* -
"/foo/bar/baz" ==> "/foo/bar" and "baz"
* - "/bar" ==> "/" and "bar"
* - "/" ==> "/" and "/"
+ * - "X:/" ==> "X:/" and "X:/"
* - "bar" ==> "" and "bar"
* - "" ==> "" and ""
*/
@@ -176,6 +177,12 @@
*/
int svn_path_is_empty(const char *path);
+/** Return TRUE if @a path is considered a root path on the platform at
+ * hand, amongst which '/' on all platforms or 'X:/', '\\?\X:/',
+ * '\\.\..', '\\server\share' on Windows.
+ */
+svn_boolean_t svn_path_is_root(const char *path, apr_size_t len,
+ apr_pool_t *pool);
/** Return a new path (or URL) like @a path, but transformed such that
* some types of path specification redundancies are removed.
Index: subversion/libsvn_client/commit.c
===================================================================
--- subversion/libsvn_client/commit.c (revision 21156)
+++ subversion/libsvn_client/commit.c (working copy)
@@ -1309,8 +1309,9 @@
while (strcmp(target, base_dir) != 0)
{
- if ((target[0] == '/' && target[1] == '\0') ||
- (target[0] == '\0'))
+ if ((target[0] == '\0') ||
+ svn_path_is_root(target, strlen(target), subpool)
+ )
abort();
APR_ARRAY_PUSH(dirs_to_lock,
Index: subversion/libsvn_subr/path.c
===================================================================
--- subversion/libsvn_subr/path.c (revision 21156)
+++ subversion/libsvn_subr/path.c (working copy)
@@ -24,6 +24,7 @@
#include
#include
+#include "svn_pools.h"
#include "svn_string.h"
#include "svn_path.h"
#include "svn_private_config.h" /* for SVN_PATH_LOCAL_SEPARATOR */
@@ -98,7 +99,8 @@
apr_size_t len)
{
return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len)
- && (len <= 1 || path[len-1] != '/'));
+ && (svn_path_is_root(path, len, NULL) ||
+ (len <= 1 || path[len-1] != '/')));
}
#endif
@@ -110,6 +112,7 @@
apr_size_t blen = strlen(base);
apr_size_t clen = strlen(component);
char *path;
+ int add_separator;
assert(is_canonical(base, blen));
assert(is_canonical(component, clen));
@@ -124,14 +127,16 @@
if (SVN_PATH_IS_EMPTY(component))
return apr_pmemdup(pool, base, blen + 1);
- if (blen == 1 && base[0] == '/')
- blen = 0; /* Ignore base, just return separator + component */
+ add_separator = 1;
+ if (svn_path_is_root(base, blen, pool))
+ add_separator = 0; /* Ignore base, just return separator + component */
/* Construct the new, combined path. */
- path = apr_palloc(pool, blen + 1 + clen + 1);
+ path = apr_palloc(pool, blen + add_separator + clen + 1);
memcpy(path, base, blen);
- path[blen] = '/';
- memcpy(path + blen + 1, component, clen + 1);
+ if (add_separator)
+ path[blen] = '/';
+ memcpy(path + blen + add_separator, component, clen + 1);
return path;
}
@@ -154,9 +159,9 @@
assert(is_canonical(base, total_len));
- if (total_len == 1 && *base == '/')
- base_is_root = TRUE;
- else if (SVN_PATH_IS_EMPTY(base))
+ base_is_root = svn_path_is_root(base, total_len, pool);
+ if (!base_is_root &&
+ (SVN_PATH_IS_EMPTY(base)))
{
total_len = sizeof(SVN_EMPTY_PATH) - 1;
base_is_empty = TRUE;
@@ -309,8 +314,9 @@
while (len > 0 && path[--len] != '/')
;
- if (len == 0 && path[0] == '/')
- return 1;
+ /* check if the remaining segment including trailing '/' is a root path */
+ if (svn_path_is_root(path, len + 1, NULL))
+ return len + 1;
else
return len;
}
@@ -376,7 +382,7 @@
assert(is_canonical(path, len));
- if (len == 1 && path[0] == '/')
+ if (svn_path_is_root(path, len, pool))
start = 0;
else
{
@@ -418,6 +424,46 @@
}
+svn_boolean_t
+svn_path_is_root(const char *path, apr_size_t len, apr_pool_t *pool)
+{
+ char *root_path = NULL;
+ apr_status_t status;
+ apr_pool_t *strpool = (pool) ? pool : svn_pool_create(NULL);
+ char *rel_path = apr_pstrmemdup(strpool, path, len);
+ const char *rel_path_apr;
+ svn_boolean_t result = FALSE;
+
+ /* svn_path_cstring_from_utf8 will create a copy of path.
+
+ It should be safe to convert this error to a false return value. An error
+ in this case would indicate that the path isn't encoded in UTF-8, which
+ will cause problems elsewhere, anyway. */
+ svn_error_t *err = svn_path_cstring_from_utf8(&rel_path_apr, rel_path,
+ strpool);
+ if (err)
+ {
+ svn_error_clear(err);
+ goto cleanup;
+ }
+
+ status = apr_filepath_root(&root_path, &rel_path, 0, strpool);
+
+ if ((status == APR_SUCCESS ||
+ status == APR_EINCOMPLETE) &&
+ rel_path[0] == '\0')
+ {
+ result = TRUE;
+ goto cleanup;
+ }
+
+ cleanup:
+ if (!pool)
+ apr_pool_destroy(strpool);
+ return result;
+}
+
+
int
svn_path_compare_paths(const char *path1,
const char *path2)
@@ -498,7 +544,8 @@
that non-matching byte. */
if (((i == path1_len) && (path2[i] == '/'))
|| ((i == path2_len) && (path1[i] == '/'))
- || ((i == path1_len) && (i == path2_len)))
+ || ((i == path1_len) && (i == path2_len))
+ || svn_path_is_root(path1, i, pool))
return i;
else
return last_dirsep;
@@ -1106,6 +1153,7 @@
apr_size_t seglen;
apr_size_t canon_segments = 0;
svn_boolean_t uri;
+ svn_boolean_t strip_slash;
dst = canon = apr_pcalloc(pool, strlen(path) + 1);
@@ -1168,18 +1216,26 @@
src++;
}
- /* Remove the trailing slash. */
- if ((canon_segments > 0 || uri) && *(dst - 1) == '/')
+ strip_slash = TRUE;
+#if defined(WIN32)
+ /* Do not strip the trailing slash in a path like this: X:/ */
+ if (canon_segments == 1 && canon[1] == ':' && canon[2] == '/')
+ strip_slash = FALSE;
+#endif /* WIN32 */
+
+ /* Remove the trailing slash if needed. */
+ if (strip_slash &&
+ (canon_segments > 0 || uri) && *(dst - 1) == '/')
dst--;
*dst = '\0';
-#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(WIN32)
/* Skip leading double slashes when there are less than 2
* canon segments. UNC paths *MUST* have two segments. */
if (canon_segments < 2 && canon[0] == '/' && canon[1] == '/')
return canon + 1;
-#endif /* WIN32 or Cygwin */
+#endif /* WIN32 */
return canon;
}
Index: subversion/tests/libsvn_subr/path-test.c
===================================================================
--- subversion/tests/libsvn_subr/path-test.c (revision 21156)
+++ subversion/tests/libsvn_subr/path-test.c (working copy)
@@ -112,6 +112,11 @@
{ "../foo", "..", "foo" },
{ SVN_EMPTY_PATH, SVN_EMPTY_PATH, SVN_EMPTY_PATH },
{ "/flu\\b/\\blarg", "/flu\\b", "\\blarg" },
+ { "/", "/", "/" },
+#if defined(WIN32)
+ { "X:/", "X:/", "X:/" },
+ { "X:/foo", "X:/", "foo" },
+#endif /* WIN32 */
};
*msg = "test svn_path_split";
@@ -453,6 +458,10 @@
{ "abc", SVN_EMPTY_PATH, "abc" },
{ SVN_EMPTY_PATH, "/abc", "/abc" },
{ SVN_EMPTY_PATH, SVN_EMPTY_PATH, SVN_EMPTY_PATH },
+#if defined(WIN32)
+ { "X:/",SVN_EMPTY_PATH, "X:/" },
+ { "X:/","abc", "X:/abc" },
+#endif /* WIN32 */
};
*msg = "test svn_path_join(_many)";
@@ -549,7 +558,11 @@
{ "/b/a", "a" },
{ "/b/a", "a" },
{ "/", "/" },
- { SVN_EMPTY_PATH, SVN_EMPTY_PATH }
+ { SVN_EMPTY_PATH, SVN_EMPTY_PATH },
+#if defined(WIN32)
+ { "X:/", "X:/" },
+ { "X:/abc", "abc" },
+#endif /* WIN32 */
};
*msg = "test svn_path_basename";
@@ -715,6 +728,10 @@
{ "foo/bar", "foo" },
{ "/foo/bar", "/foo" },
{ "/foo", "/" },
+#if defined(WIN32)
+ { "X:/foo/bar", "X:/foo" },
+ { "X:/foo", "X:/" },
+#endif /* WIN32 */
{ NULL, NULL }
};
int i;
@@ -744,6 +761,58 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+test_is_root(const char **msg,
+ svn_boolean_t msg_only,
+ svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ /* Paths to test. */
+ static const char * const paths[] = {
+ "/foo/bar",
+ "/foo",
+ "/",
+#if defined(WIN32)
+ "X:/foo",
+ "X:/",
+#endif /* WIN32 */
+ "",
+ };
+
+ /* Expected results of the tests. */
+ static const svn_boolean_t retvals[] = {
+ FALSE,
+ FALSE,
+ TRUE,
+ FALSE,
+#if defined(WIN32)
+ TRUE,
+ FALSE,
+#endif /* WIN32 */
+ };
+
+ *msg = "test svn_path_is_root";
+
+ if (msg_only)
+ return SVN_NO_ERROR;
+
+ for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++)
+ {
+ svn_boolean_t retval;
+
+ retval = svn_path_is_root(paths[i], strlen(paths[i]), pool);
+ if (retvals[i] != retval)
+ return svn_error_createf
+ (SVN_ERR_TEST_FAILED, NULL,
+ "svn_path_is_root (%s) returned %s instead of %s",
+ paths[i], retval ? "TRUE" : "FALSE", retvals[i] ? "TRUE" : "FALSE");
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* The test table. */
@@ -763,5 +832,6 @@
SVN_TEST_PASS(test_decompose),
SVN_TEST_PASS(test_canonicalize),
SVN_TEST_PASS(test_remove_component),
+ SVN_TEST_PASS(test_is_root),
SVN_TEST_NULL
};