Index: subversion/include/svn_path.h =================================================================== --- subversion/include/svn_path.h (revision 21125) +++ subversion/include/svn_path.h (working copy) @@ -176,6 +176,9 @@ */ int svn_path_is_empty(const char *path); +/** Return TRUE if @a path is a root path ("/" or "X:/") + */ +svn_boolean_t svn_path_is_root(const char *path); /** Return a new path (or URL) like @a path, but transformed such that * some types of path specification redundancies are removed. Index: subversion/libsvn_subr/path.c =================================================================== --- subversion/libsvn_subr/path.c (revision 21125) +++ subversion/libsvn_subr/path.c (working copy) @@ -97,6 +97,15 @@ is_canonical(const char *path, apr_size_t len) { +#if defined(WIN32) + /* On Windows, X:/ is a root path! */ + if (len == 3 && + ((path[0]>='A'&&path[0]<='Z') || + (path[0]>='a'&&path[0]<='z')) && + path[1] == ':' && path[2] == '/') + return TRUE; +#endif /* WIN32 */ + return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len) && (len <= 1 || path[len-1] != '/')); } @@ -110,6 +119,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 +134,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)) + 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; } @@ -364,6 +376,9 @@ assert(is_canonical(path, len)); + if (svn_path_is_root(path)) + return apr_pstrmemdup(pool, path, len); + return apr_pstrmemdup(pool, path, previous_segment(path, len)); } @@ -376,7 +391,7 @@ assert(is_canonical(path, len)); - if (len == 1 && path[0] == '/') + if (svn_path_is_root(path)) start = 0; else { @@ -417,7 +432,20 @@ return 0; } +svn_boolean_t +svn_path_is_root(const char *path) +{ +#if defined(WIN32) + /* On Windows, X:/ is a root path! */ + if (((path[0]>='A'&&path[0]<='Z') || + (path[0]>='a'&&path[0]<='z')) && + path[1] == ':' && path[2] == '/' && path[3] == 0) + return TRUE; +#endif /* WIN32 */ + return path[0] == '/' && path[1] == 0; +} + int svn_path_compare_paths(const char *path1, const char *path2) @@ -1106,6 +1134,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,8 +1197,16 @@ src++; } + strip_slash = TRUE; +#if defined(WIN32) || defined(__CYGWIN__) + /* 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 or Cygwin */ + /* Remove the trailing slash. */ - if ((canon_segments > 0 || uri) && *(dst - 1) == '/') + if (strip_slash && + (canon_segments > 0 || uri) && *(dst - 1) == '/') dst--; *dst = '\0'; Index: subversion/tests/cmdline/update_tests.py =================================================================== --- subversion/tests/cmdline/update_tests.py (revision 21125) +++ subversion/tests/cmdline/update_tests.py (working copy) @@ -2157,6 +2157,68 @@ None, None, None, None, 0, C_Path, '--force') +#---------------------------------------------------------------------- +# Test for issue #2556. The tests maps a virtual drive to a working copy +# and tries some basic update, commit and status actions on the virtual +# drive. +def update_wc_on_windows_drive(sbox): + "update wc on the root of a Windows (virtual) drive" + + def find_the_next_available_drive_letter(): + "find the first available drive" + + # get the list of used drive letters, use some Windows specific function. + import win32api + + drives=win32api.GetLogicalDriveStrings() + drives=string.splitfields(drives,'\000') + + for d in range(ord('G'), ord('Z')+1): + drive = chr(d) + if not drive + ':\\' in drives: + return drive + + return '' + + # skip this test on non-Windows platforms. + if not os.name == 'nt': + raise svntest.Skip + + sbox.build() + + # create a virtual drive to the working copy folder + drive = find_the_next_available_drive_letter() + if drive == '': + raise svntest.Skip + + os.popen3('subst ' + drive +': ' + sbox.wc_dir, 't') + wc_dir = drive + ':\\\\' + + was_cwd = os.getcwd() + os.chdir(wc_dir) + + try: + expected_disk = svntest.main.greek_state.copy() + + # Make a local mod to a file which will be committed + mu_path = os.path.join(wc_dir, 'A', 'mu') + svntest.main.file_append (mu_path, '\nAppended text for mu') + + # Commit. + expected_output = svntest.wc.State(wc_dir, { + 'A/mu' : Item(verb='Sending'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.tweak('A/mu', wc_rev=2) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status, None, + None, None, None, None, wc_dir) + finally: + os.chdir(was_cwd) + # cleanup the virtual drive + os.popen3('subst /D ' + drive +': ', 't') + ######################################################################## # Run the tests @@ -2194,7 +2256,8 @@ update_eolstyle_handling, XFail(update_copy_of_old_rev), forced_update, - forced_update_failures + forced_update_failures, + update_wc_on_windows_drive, ] if __name__ == '__main__':