From 24f1da7f309f7e7077e265e1777c9da46ff65e37 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 27 Aug 2013 18:26:22 -0700 Subject: [PATCH] Add a fancy new paths_are_equivalent function to test for equivalent paths instead of merely equal ones --- fish_tests.cpp | 10 +++++--- path.cpp | 64 ++++++++++++++++++++++++++++++++++++++++---------- path.h | 3 +++ 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/fish_tests.cpp b/fish_tests.cpp index 41464c31a..b47ce3a3a 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -766,9 +766,8 @@ static void test_path() say(L"Testing path functions"); wcstring path = L"//foo//////bar/"; - wcstring canon = path; - path_make_canonical(canon); - if (canon != L"/foo/bar") + path_make_canonical(path); + if (path != L"/foo/bar") { err(L"Bug in canonical PATH code"); } @@ -779,6 +778,11 @@ static void test_path() { err(L"Bug in canonical PATH code"); } + + if (paths_are_equivalent(L"/foo/bar/baz", L"foo/bar/baz")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__); + if (! paths_are_equivalent(L"///foo///bar/baz", L"/foo/bar////baz//")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__); + if (! paths_are_equivalent(L"/foo/bar/baz", L"/foo/bar/baz")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__); + if (! paths_are_equivalent(L"/", L"/")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__); } enum word_motion_t diff --git a/path.cpp b/path.cpp index 73cfc0171..27aadbd3e 100644 --- a/path.cpp +++ b/path.cpp @@ -380,24 +380,62 @@ static void replace_all(wcstring &str, const wchar_t *needle, const wchar_t *rep void path_make_canonical(wcstring &path) { + // Ignore trailing slashes, unless it's the first character + size_t len = path.size(); + while (len > 1 && path.at(len - 1) == L'/') + len--; - /* Remove double slashes */ - size_t size; - do + // Turn runs of slashes into a single slash + size_t trailing = 0; + bool prev_was_slash = false; + for (size_t leading = 0; leading < len; leading++) { - size = path.size(); - replace_all(path, L"//", L"/"); - } - while (path.size() != size); + wchar_t c = path.at(leading); + bool is_slash = (c == '/'); + if (! prev_was_slash || ! is_slash) + { + // This is either the first slash in a run, or not a slash at all + path.at(trailing++) = c; + } + prev_was_slash = is_slash; + } + assert(trailing <= len); + if (trailing < len) + path.resize(trailing); +} - /* Remove trailing slashes, except don't remove the first one */ - while (size-- > 1) +bool paths_are_equivalent(const wcstring &p1, const wcstring &p2) +{ + if (p1 == p2) + return true; + + size_t len1 = p1.size(), len2 = p2.size(); + + // Ignore trailing slashes after the first character + while (len1 > 1 && p1.at(len1 - 1) == L'/') len1--; + while (len2 > 1 && p2.at(len2 - 1) == L'/') len2--; + + // Start walking + size_t idx1 = 0, idx2 = 0; + while (idx1 < len1 && idx2 < len2) { - if (path.at(size) != L'/') + wchar_t c1 = p1.at(idx1), c2 = p2.at(idx2); + + // If the characters are different, the strings are not equivalent + if (c1 != c2) break; + + idx1++; + idx2++; + + // If the character was a slash, walk forwards until we hit the end of the string, or a non-slash + // Note the first condition is invariant within the loop + while (c1 == L'/' && idx1 < len1 && p1.at(idx1) == L'/') idx1++; + while (c2 == L'/' && idx2 < len2 && p2.at(idx2) == L'/') idx2++; } - /* Now size is either -1 (if the entire string was slashes) or is the index of the last non-slash character. Either way this will set it to the correct size. */ - path.resize(size+1); + + // We matched if we consumed all of the characters in both strings + return idx1 == len1 && idx2 == len2; } bool path_is_valid(const wcstring &path, const wcstring &working_directory) @@ -433,7 +471,7 @@ bool path_is_valid(const wcstring &path, const wcstring &working_directory) bool paths_are_same_file(const wcstring &path1, const wcstring &path2) { - if (path1 == path2) + if (paths_are_equivalent(path1, path2)) return true; struct stat s1, s2; diff --git a/path.h b/path.h index a566501ef..b822f6e9c 100644 --- a/path.h +++ b/path.h @@ -73,6 +73,9 @@ bool path_can_be_implicit_cd(const wcstring &path, */ void path_make_canonical(wcstring &path); +/** Check if two paths are equivalent, which means to ignore runs of multiple slashes (or trailing slashes) */ +bool paths_are_equivalent(const wcstring &p1, const wcstring &p2); + bool path_is_valid(const wcstring &path, const wcstring &working_directory); /** Returns whether the two paths refer to the same file */