diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index b9ffa848d1f..bc558fc5a84 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -385,6 +385,52 @@ static QByteArray fileIdWin(HANDLE fHandle) } #endif +FilePath FileUtils::commonPath(const FilePaths &paths) +{ + if (paths.isEmpty()) + return {}; + + if (paths.count() == 1) + return paths.constFirst(); + + const FilePath &first = paths.constFirst(); + const FilePaths others = paths.mid(1); + FilePath result; + + // Common scheme + const QString &commonScheme = first.scheme(); + auto sameScheme = [&commonScheme] (const FilePath &fp) { + return commonScheme == fp.scheme(); + }; + if (!allOf(others, sameScheme)) + return result; + result.setScheme(commonScheme); + + // Common host + const QString &commonHost = first.host(); + auto sameHost = [&commonHost] (const FilePath &fp) { + return commonHost == fp.host(); + }; + if (!allOf(others, sameHost)) + return result; + result.setHost(commonHost); + + // Common path + QString commonPath; + auto sameBasePath = [&commonPath] (const FilePath &fp) { + return QString(fp.path() + '/').startsWith(commonPath); + }; + const QStringList pathSegments = first.path().split('/'); + for (const QString &segment : pathSegments) { + commonPath += segment + '/'; + if (!allOf(others, sameBasePath)) + return result; + result.setPath(commonPath.chopped(1)); + } + + return result; +} + QByteArray FileUtils::fileId(const FilePath &fileName) { QByteArray result; diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index d852893f685..934805f52df 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -134,6 +134,7 @@ public: static bool isRelativePath(const QString &fileName); static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); } static FilePath commonPath(const FilePath &oldCommonPath, const FilePath &fileName); + static FilePath commonPath(const FilePaths &paths); static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index b291d94879f..b3cb3a3b710 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -66,6 +66,8 @@ private slots: void linkFromString(); void pathAppended_data(); void pathAppended(); + void commonPath_data(); + void commonPath(); void asyncLocalCopy(); @@ -590,6 +592,46 @@ void tst_fileutils::pathAppended_data() QTest::newRow("u4") << "a/b" << "c/d" << "a/b/c/d"; } +void tst_fileutils::commonPath() +{ + QFETCH(FilePaths, list); + QFETCH(FilePath, expected); + + const FilePath result = FileUtils::commonPath(list); + + QCOMPARE(expected.toString(), result.toString()); +} + +void tst_fileutils::commonPath_data() +{ + QTest::addColumn("list"); + QTest::addColumn("expected"); + + const FilePath p1 = FilePath::fromString("c:/Program Files(x86)"); + const FilePath p2 = FilePath::fromString("c:/Program Files(x86)/Ide"); + const FilePath p3 = FilePath::fromString("c:/Program Files(x86)/Ide/Qt Creator"); + const FilePath p4 = FilePath::fromString("c:/Program Files"); + const FilePath p5 = FilePath::fromString("c:"); + + const FilePath url1 = FilePath::fromString("http://127.0.0.1/./"); + const FilePath url2 = FilePath::fromString("https://127.0.0.1/./"); + const FilePath url3 = FilePath::fromString("http://www.qt.io/./"); + const FilePath url4 = FilePath::fromString("https://www.qt.io/./"); + const FilePath url5 = FilePath::fromString("https://www.qt.io/ide/"); + const FilePath url6 = FilePath::fromString("http:///./"); + + QTest::newRow("Zero paths") << FilePaths{} << FilePath(); + QTest::newRow("Single path") << FilePaths{ p1 } << p1; + QTest::newRow("3 identical paths") << FilePaths{ p1, p1, p1 } << p1; + QTest::newRow("3 paths, common path") << FilePaths{ p1, p2, p3 } << p1; + QTest::newRow("3 paths, no common path") << FilePaths{ p1, p2, p4 } << p5; + QTest::newRow("3 paths, first is part of second") << FilePaths{ p4, p1, p3 } << p5; + QTest::newRow("Common scheme") << FilePaths{ url1, url3 } << url6; + QTest::newRow("Different scheme") << FilePaths{ url1, url2 } << FilePath(); + QTest::newRow("Common host") << FilePaths{ url4, url5 } << url4; + QTest::newRow("Different host") << FilePaths{ url1, url3 } << url6; +} + void tst_fileutils::asyncLocalCopy() { const FilePath orig = FilePath::fromString(rootPath).pathAppended("x/y/fileToCopy.txt");