1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "vcsmanager.h"
27 #include "iversioncontrol.h"
28 #include "icore.h"
29 #include "documentmanager.h"
30 #include "idocument.h"
31
32 #include <coreplugin/dialogs/addtovcsdialog.h>
33 #include <coreplugin/editormanager/editormanager.h>
34 #include <coreplugin/editormanager/ieditor.h>
35
36 #include <extensionsystem/pluginmanager.h>
37 #include <utils/algorithm.h>
38 #include <utils/infobar.h>
39 #include <utils/optional.h>
40 #include <utils/qtcassert.h>
41 #include <vcsbase/vcsbaseconstants.h>
42
43 #include <QDir>
44 #include <QString>
45 #include <QList>
46 #include <QMap>
47
48 #include <QFileInfo>
49 #include <QMessageBox>
50
51 using namespace Utils;
52
53 namespace Core {
54
55 #if defined(WITH_TESTS)
56 const char TEST_PREFIX[] = "/8E3A9BA0-0B97-40DF-AEC1-2BDF9FC9EDBE/";
57 #endif
58
59 // ---- VCSManagerPrivate:
60 // Maintains a cache of top-level directory->version control.
61
62 class VcsManagerPrivate
63 {
64 public:
65 class VcsInfo {
66 public:
67 VcsInfo() = default;
VcsInfo(IVersionControl * vc,const QString & tl)68 VcsInfo(IVersionControl *vc, const QString &tl) :
69 versionControl(vc), topLevel(tl)
70 { }
71 VcsInfo(const VcsInfo &other) = default;
72
operator ==(const VcsInfo & other) const73 bool operator == (const VcsInfo &other) const
74 {
75 return versionControl == other.versionControl && topLevel == other.topLevel;
76 }
77
78 IVersionControl *versionControl = nullptr;
79 QString topLevel;
80 };
81
findInCache(const QString & dir) const82 Utils::optional<VcsInfo> findInCache(const QString &dir) const
83 {
84 QTC_ASSERT(QDir(dir).isAbsolute(), return Utils::nullopt);
85 QTC_ASSERT(!dir.endsWith(QLatin1Char('/')), return Utils::nullopt);
86 QTC_ASSERT(QDir::fromNativeSeparators(dir) == dir, return Utils::nullopt);
87
88 const auto it = m_cachedMatches.constFind(dir);
89 return it == m_cachedMatches.constEnd() ? Utils::nullopt : Utils::make_optional(it.value());
90 }
91
clearCache()92 void clearCache()
93 {
94 m_cachedMatches.clear();
95 }
96
resetCache(const QString & dir)97 void resetCache(const QString &dir)
98 {
99 QTC_ASSERT(QDir(dir).isAbsolute(), return);
100 QTC_ASSERT(!dir.endsWith(QLatin1Char('/')), return);
101 QTC_ASSERT(QDir::fromNativeSeparators(dir) == dir, return);
102
103 const QString dirSlash = dir + QLatin1Char('/');
104 foreach (const QString &key, m_cachedMatches.keys()) {
105 if (key == dir || key.startsWith(dirSlash))
106 m_cachedMatches.remove(key);
107 }
108 }
109
cache(IVersionControl * vc,const QString & topLevel,const QString & dir)110 void cache(IVersionControl *vc, const QString &topLevel, const QString &dir)
111 {
112 QTC_ASSERT(QDir(dir).isAbsolute(), return);
113 QTC_ASSERT(!dir.endsWith(QLatin1Char('/')), return);
114 QTC_ASSERT(QDir::fromNativeSeparators(dir) == dir, return);
115 QTC_ASSERT(dir.startsWith(topLevel + QLatin1Char('/'))
116 || topLevel == dir || topLevel.isEmpty(), return);
117 QTC_ASSERT((topLevel.isEmpty() && !vc) || (!topLevel.isEmpty() && vc), return);
118
119 QString tmpDir = dir;
120 const QChar slash = QLatin1Char('/');
121 while (tmpDir.count() >= topLevel.count() && !tmpDir.isEmpty()) {
122 m_cachedMatches.insert(tmpDir, VcsInfo(vc, topLevel));
123 // if no vc was found, this might mean we're inside a repo internal directory (.git)
124 // Cache only input directory, not parents
125 if (!vc)
126 break;
127 const int slashPos = tmpDir.lastIndexOf(slash);
128 if (slashPos >= 0)
129 tmpDir.truncate(slashPos);
130 else
131 tmpDir.clear();
132 }
133 }
134
135 QList<IVersionControl *> m_versionControlList;
136 QMap<QString, VcsInfo> m_cachedMatches;
137 IVersionControl *m_unconfiguredVcs = nullptr;
138
139 QStringList m_cachedAdditionalToolsPaths;
140 bool m_cachedAdditionalToolsPathsDirty = true;
141 };
142
143 static VcsManagerPrivate *d = nullptr;
144 static VcsManager *m_instance = nullptr;
145
VcsManager(QObject * parent)146 VcsManager::VcsManager(QObject *parent) :
147 QObject(parent)
148 {
149 m_instance = this;
150 d = new VcsManagerPrivate;
151 }
152
153 // ---- VCSManager:
154
~VcsManager()155 VcsManager::~VcsManager()
156 {
157 m_instance = nullptr;
158 delete d;
159 }
160
addVersionControl(IVersionControl * vc)161 void VcsManager::addVersionControl(IVersionControl *vc)
162 {
163 QTC_ASSERT(!d->m_versionControlList.contains(vc), return);
164 d->m_versionControlList.append(vc);
165 }
166
instance()167 VcsManager *VcsManager::instance()
168 {
169 return m_instance;
170 }
171
extensionsInitialized()172 void VcsManager::extensionsInitialized()
173 {
174 // Change signal connections
175 foreach (IVersionControl *versionControl, versionControls()) {
176 connect(versionControl, &IVersionControl::filesChanged, DocumentManager::instance(),
177 [](const QStringList fileNames) {
178 DocumentManager::notifyFilesChangedInternally(
179 Utils::transform(fileNames, &Utils::FilePath::fromString));
180 });
181 connect(versionControl, &IVersionControl::repositoryChanged,
182 m_instance, &VcsManager::repositoryChanged);
183 connect(versionControl, &IVersionControl::configurationChanged,
184 m_instance, &VcsManager::handleConfigurationChanges);
185 }
186 }
187
versionControls()188 const QList<IVersionControl *> VcsManager::versionControls()
189 {
190 return d->m_versionControlList;
191 }
192
versionControl(Id id)193 IVersionControl *VcsManager::versionControl(Id id)
194 {
195 return Utils::findOrDefault(versionControls(), Utils::equal(&Core::IVersionControl::id, id));
196 }
197
absoluteWithNoTrailingSlash(const QString & directory)198 static QString absoluteWithNoTrailingSlash(const QString &directory)
199 {
200 QString res = QDir(directory).absolutePath();
201 if (res.endsWith(QLatin1Char('/')))
202 res.chop(1);
203 return res;
204 }
205
resetVersionControlForDirectory(const QString & inputDirectory)206 void VcsManager::resetVersionControlForDirectory(const QString &inputDirectory)
207 {
208 if (inputDirectory.isEmpty())
209 return;
210
211 const QString directory = absoluteWithNoTrailingSlash(inputDirectory);
212 d->resetCache(directory);
213 emit m_instance->repositoryChanged(directory);
214 }
215
findVersionControlForDirectory(const QString & inputDirectory,QString * topLevelDirectory)216 IVersionControl* VcsManager::findVersionControlForDirectory(const QString &inputDirectory,
217 QString *topLevelDirectory)
218 {
219 using StringVersionControlPair = QPair<QString, IVersionControl *>;
220 using StringVersionControlPairs = QList<StringVersionControlPair>;
221 if (inputDirectory.isEmpty()) {
222 if (topLevelDirectory)
223 topLevelDirectory->clear();
224 return nullptr;
225 }
226
227 // Make sure we an absolute path:
228 QString directory = absoluteWithNoTrailingSlash(inputDirectory);
229 #ifdef WITH_TESTS
230 if (directory[0].isLetter() && directory.indexOf(QLatin1Char(':') + QLatin1String(TEST_PREFIX)) == 1)
231 directory = directory.mid(2);
232 #endif
233 auto cachedData = d->findInCache(directory);
234 if (cachedData) {
235 if (topLevelDirectory)
236 *topLevelDirectory = cachedData->topLevel;
237 return cachedData->versionControl;
238 }
239
240 // Nothing: ask the IVersionControls directly.
241 StringVersionControlPairs allThatCanManage;
242
243 foreach (IVersionControl * versionControl, versionControls()) {
244 QString topLevel;
245 if (versionControl->managesDirectory(directory, &topLevel))
246 allThatCanManage.push_back(StringVersionControlPair(topLevel, versionControl));
247 }
248
249 // To properly find a nested repository (say, git checkout inside SVN),
250 // we need to select the version control with the longest toplevel pathname.
251 Utils::sort(allThatCanManage, [](const StringVersionControlPair &l,
252 const StringVersionControlPair &r) {
253 return l.first.size() > r.first.size();
254 });
255
256 if (allThatCanManage.isEmpty()) {
257 d->cache(nullptr, QString(), directory); // register that nothing was found!
258
259 // report result;
260 if (topLevelDirectory)
261 topLevelDirectory->clear();
262 return nullptr;
263 }
264
265 // Register Vcs(s) with the cache
266 QString tmpDir = absoluteWithNoTrailingSlash(directory);
267 #if defined WITH_TESTS
268 // Force caching of test directories (even though they do not exist):
269 if (directory.startsWith(QLatin1String(TEST_PREFIX)))
270 tmpDir = directory;
271 #endif
272 // directory might refer to a historical directory which doesn't exist.
273 // In this case, don't cache it.
274 if (!tmpDir.isEmpty()) {
275 const QChar slash = QLatin1Char('/');
276 const StringVersionControlPairs::const_iterator cend = allThatCanManage.constEnd();
277 for (StringVersionControlPairs::const_iterator i = allThatCanManage.constBegin(); i != cend; ++i) {
278 // If topLevel was already cached for another VC, skip this one
279 if (tmpDir.count() < i->first.count())
280 continue;
281 d->cache(i->second, i->first, tmpDir);
282 tmpDir = i->first;
283 const int slashPos = tmpDir.lastIndexOf(slash);
284 if (slashPos >= 0)
285 tmpDir.truncate(slashPos);
286 }
287 }
288
289 // return result
290 if (topLevelDirectory)
291 *topLevelDirectory = allThatCanManage.first().first;
292 IVersionControl *versionControl = allThatCanManage.first().second;
293 const bool isVcsConfigured = versionControl->isConfigured();
294 if (!isVcsConfigured || d->m_unconfiguredVcs) {
295 Id vcsWarning("VcsNotConfiguredWarning");
296 IDocument *curDocument = EditorManager::currentDocument();
297 if (isVcsConfigured) {
298 if (curDocument && d->m_unconfiguredVcs == versionControl) {
299 curDocument->infoBar()->removeInfo(vcsWarning);
300 d->m_unconfiguredVcs = nullptr;
301 }
302 return versionControl;
303 } else {
304 Utils::InfoBar *infoBar = curDocument ? curDocument->infoBar() : nullptr;
305 if (infoBar && infoBar->canInfoBeAdded(vcsWarning)) {
306 Utils::InfoBarEntry info(vcsWarning,
307 tr("%1 repository was detected but %1 is not configured.")
308 .arg(versionControl->displayName()),
309 Utils::InfoBarEntry::GlobalSuppression::Enabled);
310 d->m_unconfiguredVcs = versionControl;
311 info.setCustomButtonInfo(ICore::msgShowOptionsDialog(), []() {
312 QTC_ASSERT(d->m_unconfiguredVcs, return);
313 ICore::showOptionsDialog(d->m_unconfiguredVcs->id());
314 });
315
316 infoBar->addInfo(info);
317 }
318 return nullptr;
319 }
320 }
321 return versionControl;
322 }
323
findTopLevelForDirectory(const QString & directory)324 QString VcsManager::findTopLevelForDirectory(const QString &directory)
325 {
326 QString result;
327 findVersionControlForDirectory(directory, &result);
328 return result;
329 }
330
repositories(const IVersionControl * vc)331 QStringList VcsManager::repositories(const IVersionControl *vc)
332 {
333 QStringList result;
334 for (auto it = d->m_cachedMatches.constBegin(); it != d->m_cachedMatches.constEnd(); ++it) {
335 if (it.value().versionControl == vc)
336 result.append(it.value().topLevel);
337 }
338 return result;
339 }
340
promptToDelete(IVersionControl * versionControl,const QString & fileName)341 bool VcsManager::promptToDelete(IVersionControl *versionControl, const QString &fileName)
342 {
343 return promptToDelete(versionControl, {Utils::FilePath::fromString(fileName)}).isEmpty();
344 }
345
promptToDelete(const FilePaths & filePaths)346 FilePaths VcsManager::promptToDelete(const FilePaths &filePaths)
347 {
348 // Categorize files by their parent directory, so we won't call
349 // findVersionControlForDirectory() more often than necessary.
350 QMap<FilePath, FilePaths> filesByParentDir;
351 for (const FilePath &fp : filePaths)
352 filesByParentDir[fp.absolutePath()].append(fp);
353
354 // Categorize by version control system.
355 QHash<IVersionControl *, FilePaths> filesByVersionControl;
356 for (auto it = filesByParentDir.cbegin(); it != filesByParentDir.cend(); ++it) {
357 IVersionControl * const vc = findVersionControlForDirectory(it.key().toString());
358 if (vc)
359 filesByVersionControl[vc] << it.value();
360 }
361
362 // Remove the files.
363 FilePaths failedFiles;
364 for (auto it = filesByVersionControl.cbegin(); it != filesByVersionControl.cend(); ++it)
365 failedFiles << promptToDelete(it.key(), it.value());
366
367 return failedFiles;
368 }
369
promptToDelete(IVersionControl * vc,const FilePaths & filePaths)370 FilePaths VcsManager::promptToDelete(IVersionControl *vc, const FilePaths &filePaths)
371 {
372 QTC_ASSERT(vc, return {});
373 if (!vc->supportsOperation(IVersionControl::DeleteOperation))
374 return {};
375
376 const QString fileListForUi = "<ul><li>" + transform(filePaths, [](const FilePath &fp) {
377 return fp.toUserOutput();
378 }).join("</li><li>") + "</li></ul>";
379 const QString title = tr("Version Control");
380 const QString msg = tr("Remove the following files from the version control system (%2)?"
381 "%1Note: This might remove the local file.").arg(fileListForUi, vc->displayName());
382 const QMessageBox::StandardButton button =
383 QMessageBox::question(ICore::dialogParent(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
384 if (button != QMessageBox::Yes)
385 return {};
386
387 FilePaths failedFiles;
388 for (const FilePath &fp : filePaths) {
389 if (!vc->vcsDelete(fp.toString()))
390 failedFiles << fp;
391 }
392 return failedFiles;
393 }
394
msgAddToVcsTitle()395 QString VcsManager::msgAddToVcsTitle()
396 {
397 return tr("Add to Version Control");
398 }
399
msgPromptToAddToVcs(const QStringList & files,const IVersionControl * vc)400 QString VcsManager::msgPromptToAddToVcs(const QStringList &files, const IVersionControl *vc)
401 {
402 return files.size() == 1
403 ? tr("Add the file\n%1\nto version control (%2)?")
404 .arg(files.front(), vc->displayName())
405 : tr("Add the files\n%1\nto version control (%2)?")
406 .arg(files.join(QString(QLatin1Char('\n'))), vc->displayName());
407 }
408
msgAddToVcsFailedTitle()409 QString VcsManager::msgAddToVcsFailedTitle()
410 {
411 return tr("Adding to Version Control Failed");
412 }
413
msgToAddToVcsFailed(const QStringList & files,const IVersionControl * vc)414 QString VcsManager::msgToAddToVcsFailed(const QStringList &files, const IVersionControl *vc)
415 {
416 return files.size() == 1
417 ? tr("Could not add the file\n%1\nto version control (%2)\n")
418 .arg(files.front(), vc->displayName())
419 : tr("Could not add the following files to version control (%1)\n%2")
420 .arg(vc->displayName(), files.join(QString(QLatin1Char('\n'))));
421 }
422
additionalToolsPath()423 QStringList VcsManager::additionalToolsPath()
424 {
425 if (d->m_cachedAdditionalToolsPathsDirty) {
426 d->m_cachedAdditionalToolsPaths.clear();
427 foreach (IVersionControl *vc, versionControls())
428 d->m_cachedAdditionalToolsPaths.append(vc->additionalToolsPath());
429 d->m_cachedAdditionalToolsPathsDirty = false;
430 }
431 return d->m_cachedAdditionalToolsPaths;
432 }
433
promptToAdd(const QString & directory,const QStringList & fileNames)434 void VcsManager::promptToAdd(const QString &directory, const QStringList &fileNames)
435 {
436 IVersionControl *vc = findVersionControlForDirectory(directory);
437 if (!vc || !vc->supportsOperation(IVersionControl::AddOperation))
438 return;
439
440 const QStringList unmanagedFiles = vc->unmanagedFiles(fileNames);
441 if (unmanagedFiles.isEmpty())
442 return;
443
444 Internal::AddToVcsDialog dlg(ICore::dialogParent(), VcsManager::msgAddToVcsTitle(),
445 unmanagedFiles, vc->displayName());
446 if (dlg.exec() == QDialog::Accepted) {
447 QStringList notAddedToVc;
448 foreach (const QString &file, unmanagedFiles) {
449 if (!vc->vcsAdd(QDir(directory).filePath(file)))
450 notAddedToVc << file;
451 }
452
453 if (!notAddedToVc.isEmpty()) {
454 QMessageBox::warning(ICore::dialogParent(),
455 VcsManager::msgAddToVcsFailedTitle(),
456 VcsManager::msgToAddToVcsFailed(notAddedToVc, vc));
457 }
458 }
459 }
460
emitRepositoryChanged(const QString & repository)461 void VcsManager::emitRepositoryChanged(const QString &repository)
462 {
463 emit m_instance->repositoryChanged(repository);
464 }
465
clearVersionControlCache()466 void VcsManager::clearVersionControlCache()
467 {
468 QStringList repoList = d->m_cachedMatches.keys();
469 d->clearCache();
470 foreach (const QString &repo, repoList)
471 emit m_instance->repositoryChanged(repo);
472 }
473
handleConfigurationChanges()474 void VcsManager::handleConfigurationChanges()
475 {
476 d->m_cachedAdditionalToolsPathsDirty = true;
477 auto vcs = qobject_cast<IVersionControl *>(sender());
478 if (vcs)
479 emit configurationChanged(vcs);
480 }
481
482 } // namespace Core
483
484 #if defined(WITH_TESTS)
485
486 #include <QtTest>
487
488 #include "coreplugin.h"
489
490 #include <extensionsystem/pluginmanager.h>
491
492 namespace Core {
493 namespace Internal {
494
495 const char ID_VCS_A[] = "A";
496 const char ID_VCS_B[] = "B";
497
498 using FileHash = QHash<QString, QString>;
499
makeHash(const QStringList & list)500 static FileHash makeHash(const QStringList &list)
501 {
502 FileHash result;
503 foreach (const QString &i, list) {
504 QStringList parts = i.split(QLatin1Char(':'));
505 QTC_ASSERT(parts.count() == 2, continue);
506 result.insert(QString::fromLatin1(TEST_PREFIX) + parts.at(0),
507 QString::fromLatin1(TEST_PREFIX) + parts.at(1));
508 }
509 return result;
510 }
511
makeString(const QString & s)512 static QString makeString(const QString &s)
513 {
514 if (s.isEmpty())
515 return QString();
516 return QString::fromLatin1(TEST_PREFIX) + s;
517 }
518
testVcsManager_data()519 void CorePlugin::testVcsManager_data()
520 {
521 // avoid conflicts with real files and directories:
522
523 QTest::addColumn<QStringList>("dirsVcsA"); // <directory>:<toplevel>
524 QTest::addColumn<QStringList>("dirsVcsB"); // <directory>:<toplevel>
525 // <directory>:<toplevel>:<vcsid>:<- from cache, * from VCS>
526 QTest::addColumn<QStringList>("results");
527
528 QTest::newRow("A and B next to each other")
529 << QStringList({"a:a", "a/1:a", "a/2:a", "a/2/5:a", "a/2/5/6:a"})
530 << QStringList({"b:b", "b/3:b", "b/4:b"})
531 << QStringList({":::-", // empty directory to look up
532 "c:::*", // Neither in A nor B
533 "a:a:A:*", // in A
534 "b:b:B:*", // in B
535 "b/3:b:B:*", // in B
536 "b/4:b:B:*", // in B
537 "a/1:a:A:*", // in A
538 "a/2:a:A:*", // in A
539 ":::-", // empty directory to look up
540 "a/2/5/6:a:A:*", // in A
541 "a/2/5:a:A:-", // in A (cached from before!)
542 // repeat: These need to come from the cache now:
543 "c:::-", // Neither in A nor B
544 "a:a:A:-", // in A
545 "b:b:B:-", // in B
546 "b/3:b:B:-", // in B
547 "b/4:b:B:-", // in B
548 "a/1:a:A:-", // in A
549 "a/2:a:A:-", // in A
550 "a/2/5/6:a:A:-", // in A
551 "a/2/5:a:A:-" // in A
552 });
553 QTest::newRow("B in A")
554 << QStringList({"a:a", "a/1:a", "a/2:a", "a/2/5:a", "a/2/5/6:a"})
555 << QStringList({"a/1/b:a/1/b", "a/1/b/3:a/1/b", "a/1/b/4:a/1/b", "a/1/b/3/5:a/1/b",
556 "a/1/b/3/5/6:a/1/b"})
557 << QStringList({"a:a:A:*", // in A
558 "c:::*", // Neither in A nor B
559 "a/3:::*", // Neither in A nor B
560 "a/1/b/x:::*", // Neither in A nor B
561 "a/1/b:a/1/b:B:*", // in B
562 "a/1:a:A:*", // in A
563 "a/1/b/../../2:a:A:*" // in A
564 });
565 QTest::newRow("A and B") // first one wins...
566 << QStringList({"a:a", "a/1:a", "a/2:a"})
567 << QStringList({"a:a", "a/1:a", "a/2:a"})
568 << QStringList({"a/2:a:A:*"});
569 }
570
testVcsManager()571 void CorePlugin::testVcsManager()
572 {
573 // setup:
574 QList<IVersionControl *> orig = Core::d->m_versionControlList;
575 TestVersionControl *vcsA(new TestVersionControl(ID_VCS_A, QLatin1String("A")));
576 TestVersionControl *vcsB(new TestVersionControl(ID_VCS_B, QLatin1String("B")));
577
578 Core::d->m_versionControlList = {vcsA, vcsB};
579
580 // test:
581 QFETCH(QStringList, dirsVcsA);
582 QFETCH(QStringList, dirsVcsB);
583 QFETCH(QStringList, results);
584
585 vcsA->setManagedDirectories(makeHash(dirsVcsA));
586 vcsB->setManagedDirectories(makeHash(dirsVcsB));
587
588 QString realTopLevel = QLatin1String("ABC"); // Make sure this gets cleared if needed.
589
590 // From VCSes:
591 int expectedCount = 0;
592 foreach (const QString &result, results) {
593 // qDebug() << "Expecting:" << result;
594
595 QStringList split = result.split(QLatin1Char(':'));
596 QCOMPARE(split.count(), 4);
597 QVERIFY(split.at(3) == QLatin1String("*") || split.at(3) == QLatin1String("-"));
598
599
600 const QString directory = split.at(0);
601 const QString topLevel = split.at(1);
602 const QString vcsId = split.at(2);
603 bool fromCache = split.at(3) == QLatin1String("-");
604
605 if (!fromCache && !directory.isEmpty())
606 ++expectedCount;
607
608 IVersionControl *vcs;
609 vcs = VcsManager::findVersionControlForDirectory(makeString(directory), &realTopLevel);
610 QCOMPARE(realTopLevel, makeString(topLevel));
611 if (vcs)
612 QCOMPARE(vcs->id().toString(), vcsId);
613 else
614 QCOMPARE(QString(), vcsId);
615 QCOMPARE(vcsA->dirCount(), expectedCount);
616 QCOMPARE(vcsA->fileCount(), 0);
617 QCOMPARE(vcsB->dirCount(), expectedCount);
618 QCOMPARE(vcsB->fileCount(), 0);
619 }
620
621 // teardown:
622 qDeleteAll(Core::d->m_versionControlList);
623 Core::d->m_versionControlList = orig;
624 }
625
626 } // namespace Internal
627 } // namespace Core
628
629 #endif
630