1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 AudioCodes Ltd.
4 ** Author: Orgad Shaneh <orgad.shaneh@audiocodes.com>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Creator.
8 **
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ****************************************************************************/
26 
27 #include "clearcaseplugin.h"
28 #include "activityselector.h"
29 #include "checkoutdialog.h"
30 #include "clearcaseconstants.h"
31 #include "clearcaseeditor.h"
32 #include "clearcasesubmiteditor.h"
33 #include "clearcasesubmiteditorwidget.h"
34 #include "clearcasesync.h"
35 #include "settingspage.h"
36 #include "versionselector.h"
37 #include "ui_undocheckout.h"
38 
39 #include <coreplugin/actionmanager/actioncontainer.h>
40 #include <coreplugin/actionmanager/actionmanager.h>
41 #include <coreplugin/actionmanager/command.h>
42 #include <coreplugin/coreconstants.h>
43 #include <coreplugin/documentmanager.h>
44 #include <coreplugin/editormanager/documentmodel.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/icore.h>
47 #include <coreplugin/locator/commandlocator.h>
48 #include <coreplugin/messagemanager.h>
49 #include <coreplugin/progressmanager/progressmanager.h>
50 
51 #include <texteditor/textdocument.h>
52 
53 #include <projectexplorer/project.h>
54 #include <projectexplorer/projectmanager.h>
55 #include <projectexplorer/session.h>
56 
57 #include <utils/algorithm.h>
58 #include <utils/fileutils.h>
59 #include <utils/hostosinfo.h>
60 #include <utils/infobar.h>
61 #include <utils/parameteraction.h>
62 #include <utils/qtcassert.h>
63 #include <utils/qtcprocess.h>
64 #include <utils/runextensions.h>
65 #include <utils/stringutils.h>
66 #include <utils/temporarydirectory.h>
67 
68 #include <vcsbase/basevcseditorfactory.h>
69 #include <vcsbase/basevcssubmiteditorfactory.h>
70 #include <vcsbase/vcsbaseeditor.h>
71 #include <vcsbase/vcscommand.h>
72 #include <vcsbase/vcsoutputwindow.h>
73 #include <vcsbase/vcsbasesubmiteditor.h>
74 #include <vcsbase/vcsbaseplugin.h>
75 
76 #include <QAction>
77 #include <QDebug>
78 #include <QDialog>
79 #include <QDialogButtonBox>
80 #include <QDir>
81 #include <QFileInfo>
82 #include <QFuture>
83 #include <QFutureInterface>
84 #include <QInputDialog>
85 #include <QList>
86 #include <QMainWindow>
87 #include <QMenu>
88 #include <QMessageBox>
89 #include <QMetaObject>
90 #include <QMutex>
91 #include <QProcess>
92 #include <QRegularExpression>
93 #include <QSharedPointer>
94 #include <QTextCodec>
95 #include <QUrl>
96 #include <QUuid>
97 #include <QVariant>
98 #include <QVBoxLayout>
99 #include <QXmlStreamReader>
100 #ifdef WITH_TESTS
101 #include <QTest>
102 #include <coreplugin/vcsmanager.h>
103 #endif
104 
105 using namespace Core;
106 using namespace ProjectExplorer;
107 using namespace VcsBase;
108 using namespace Utils;
109 using namespace std::placeholders;
110 
111 namespace ClearCase {
112 namespace Internal {
113 
114 static const char CLEARCASE_CONTEXT[]         = "ClearCase Context";
115 static const char CMD_ID_CLEARCASE_MENU[]     = "ClearCase.Menu";
116 static const char CMD_ID_CHECKOUT[]           = "ClearCase.CheckOut";
117 static const char CMD_ID_CHECKIN[]            = "ClearCase.CheckInCurrent";
118 static const char CMD_ID_UNDOCHECKOUT[]       = "ClearCase.UndoCheckOut";
119 static const char CMD_ID_UNDOHIJACK[]         = "ClearCase.UndoHijack";
120 static const char CMD_ID_DIFF_CURRENT[]       = "ClearCase.DiffCurrent";
121 static const char CMD_ID_HISTORY_CURRENT[]    = "ClearCase.HistoryCurrent";
122 static const char CMD_ID_ANNOTATE[]           = "ClearCase.Annotate";
123 static const char CMD_ID_ADD_FILE[]           = "ClearCase.AddFile";
124 static const char CMD_ID_DIFF_ACTIVITY[]      = "ClearCase.DiffActivity";
125 static const char CMD_ID_CHECKIN_ACTIVITY[]   = "ClearCase.CheckInActivity";
126 static const char CMD_ID_UPDATEINDEX[]        = "ClearCase.UpdateIndex";
127 static const char CMD_ID_UPDATE_VIEW[]        = "ClearCase.UpdateView";
128 static const char CMD_ID_CHECKIN_ALL[]        = "ClearCase.CheckInAll";
129 static const char CMD_ID_STATUS[]             = "ClearCase.Status";
130 
131 class ClearCaseResponse
132 {
133 public:
134     bool error = false;
135     QString stdOut;
136     QString stdErr;
137     QString message;
138 };
139 
140 const VcsBaseEditorParameters logEditorParameters {
141     LogOutput,
142     "ClearCase File Log Editor",   // id
143     QT_TRANSLATE_NOOP("VCS", "ClearCase File Log Editor"),   // display_name
144     "text/vnd.qtcreator.clearcase.log"
145 };
146 
147 const VcsBaseEditorParameters annotateEditorParameters {
148     AnnotateOutput,
149     "ClearCase Annotation Editor",  // id
150     QT_TRANSLATE_NOOP("VCS", "ClearCase Annotation Editor"),   // display_name
151     "text/vnd.qtcreator.clearcase.annotation"
152 };
153 
154 const VcsBaseEditorParameters diffEditorParameters {
155     DiffOutput,
156     "ClearCase Diff Editor",  // id
157     QT_TRANSLATE_NOOP("VCS", "ClearCase Diff Editor"),   // display_name
158     "text/x-patch"
159 };
160 
161 const VcsBaseSubmitEditorParameters submitParameters {
162     Constants::CLEARCASE_SUBMIT_MIMETYPE,
163     Constants::CLEARCASECHECKINEDITOR_ID,
164     Constants::CLEARCASECHECKINEDITOR_DISPLAY_NAME,
165     VcsBaseSubmitEditorParameters::DiffFiles
166 };
167 
debugCodec(const QTextCodec * c)168 static QString debugCodec(const QTextCodec *c)
169 {
170     return c ? QString::fromLatin1(c->name()) : QString::fromLatin1("Null codec");
171 }
172 
173 class ClearCasePluginPrivate final : public VcsBase::VcsBasePluginPrivate
174 {
175     Q_OBJECT
176     enum { SilentRun = VcsBase::VcsCommand::NoOutput | VcsBase::VcsCommand::FullySynchronously };
177 
178 public:
179     ClearCasePluginPrivate();
180     ~ClearCasePluginPrivate() final;
181 
182     // IVersionControl
183     QString displayName() const final;
184     Utils::Id id() const final;
185 
186     bool isVcsFileOrDirectory(const Utils::FilePath &fileName) const final;
187 
188     bool managesDirectory(const QString &directory, QString *topLevel) const final;
189     bool managesFile(const QString &workingDirectory, const QString &fileName) const final;
190 
191     bool isConfigured() const final;
192 
193     bool supportsOperation(Operation operation) const final;
194     OpenSupportMode openSupportMode(const QString &fileName) const final;
195     bool vcsOpen(const QString &fileName) final;
196     SettingsFlags settingsFlags() const final;
197     bool vcsAdd(const QString &fileName) final;
198     bool vcsDelete(const QString &filename) final;
199     bool vcsMove(const QString &from, const QString &to) final;
200     bool vcsCreateRepository(const QString &directory) final;
201 
202     void vcsAnnotate(const QString &file, int line) final;
203     void vcsDescribe(const QString &source, const QString &changeNr) final;
204 
205     QString vcsOpenText() const final;
206     QString vcsMakeWritableText() const final;
207     QString vcsTopic(const QString &directory) final;
208 
209     ///
210     ClearCaseSubmitEditor *openClearCaseSubmitEditor(const QString &fileName, bool isUcm);
211 
212     const ClearCaseSettings &settings() const;
213     void setSettings(const ClearCaseSettings &s);
214 
215     // IVersionControl
216     bool vcsOpen(const QString &workingDir, const QString &fileName);
217     bool vcsAdd(const QString &workingDir, const QString &fileName);
218     bool vcsDelete(const QString &workingDir, const QString &fileName);
219     bool vcsCheckIn(const QString &workingDir, const QStringList &files, const QString &activity,
220                     bool isIdentical, bool isPreserve, bool replaceActivity);
221     bool vcsUndoCheckOut(const QString &workingDir, const QString &fileName, bool keep);
222     bool vcsUndoHijack(const QString &workingDir, const QString &fileName, bool keep);
223     bool vcsMove(const QString &workingDir, const QString &from, const QString &to);
224     bool vcsSetActivity(const QString &workingDir, const QString &title, const QString &activity);
225 
226     static ClearCasePluginPrivate *instance();
227 
228     QString ccGetCurrentActivity() const;
229     QList<QStringPair> activities(int *current = nullptr);
230     QString ccGetPredecessor(const QString &version) const;
231     QStringList ccGetActiveVobs() const;
232     ViewData ccGetView(const QString &workingDir) const;
233     QString ccGetComment(const QString &workingDir, const QString &fileName) const;
234     bool ccFileOp(const QString &workingDir, const QString &title, const QStringList &args,
235                   const QString &fileName, const QString &file2 = QString());
236     FileStatus vcsStatus(const QString &file) const;
237     void checkAndReIndexUnknownFile(const QString &file);
currentView() const238     QString currentView() const { return m_viewData.name; }
viewRoot() const239     QString viewRoot() const { return m_viewData.root; }
240     void refreshActivities();
isUcm() const241     inline bool isUcm() const { return m_viewData.isUcm; }
isDynamic() const242     inline bool isDynamic() const { return m_viewData.isDynamic; }
243     void setStatus(const QString &file, FileStatus::Status status, bool update = true);
244 
245     bool ccCheckUcm(const QString &viewname, const QString &workingDir) const;
246 #ifdef WITH_TESTS
setFakeCleartool(const bool b=true)247     inline void setFakeCleartool(const bool b = true) { m_fakeClearTool = b; }
248 #endif
249 
250     void vcsAnnotateHelper(const QString &workingDir, const QString &file,
251                            const QString &revision = QString(), int lineNumber = -1) const;
252     bool newActivity();
253     void updateStreamAndView();
254 
255 protected:
256     void updateActions(VcsBase::VcsBasePluginPrivate::ActionState) override;
257     bool submitEditorAboutToClose() override;
258     QString ccGet(const QString &workingDir, const QString &file, const QString &prefix = QString());
259     QList<QStringPair> ccGetActivities() const;
260 
261 private:
262     void syncSlot();
263     Q_INVOKABLE void updateStatusActions();
264 
265     QString commitDisplayName() const final;
266     void checkOutCurrentFile();
267     void addCurrentFile();
268     void undoCheckOutCurrent();
269     void undoHijackCurrent();
270     void diffActivity();
271     void diffCurrentFile();
272     void startCheckInAll();
273     void startCheckInActivity();
274     void startCheckInCurrentFile();
275     void historyCurrentFile();
276     void annotateCurrentFile();
277     void viewStatus();
278     void commitFromEditor() override;
279     void diffCheckInFiles(const QStringList &);
280     void updateIndex();
281     void updateView();
282     void projectChanged(ProjectExplorer::Project *project);
283     void tasksFinished(Utils::Id type);
284     void closing();
285 
286     inline bool isCheckInEditorOpen() const;
287     QStringList getVobList() const;
288     QString ccManagesDirectory(const QString &directory) const;
289     QString ccViewRoot(const QString &directory) const;
290     QString findTopLevel(const QString &directory) const;
291     Core::IEditor *showOutputInEditor(const QString& title, const QString &output,
292                                       Id id, const QString &source,
293                                       QTextCodec *codec) const;
294     QString runCleartoolSync(const QString &workingDir, const QStringList &arguments) const;
295     ClearCaseResponse runCleartool(const QString &workingDir, const QStringList &arguments,
296                                    int timeOutS, unsigned flags,
297                                    QTextCodec *outputCodec = nullptr) const;
298     static void sync(QFutureInterface<void> &future, QStringList files);
299 
300     void history(const QString &workingDir,
301                  const QStringList &file = QStringList(),
302                  bool enableAnnotationContextMenu = false);
303     QString ccGetFileVersion(const QString &workingDir, const QString &file) const;
304     void ccUpdate(const QString &workingDir, const QStringList &relativePaths = QStringList());
305     void ccDiffWithPred(const QString &workingDir, const QStringList &files);
306     void startCheckIn(const QString &workingDir, const QStringList &files = QStringList());
307     void cleanCheckInMessageFile();
308     QString ccGetFileActivity(const QString &workingDir, const QString &file);
309     QStringList ccGetActivityVersions(const QString &workingDir, const QString &activity);
310     void diffGraphical(const QString &file1, const QString &file2 = QString());
311     QString diffExternal(QString file1, QString file2 = QString(), bool keep = false);
312     QString getFile(const QString &nativeFile, const QString &prefix);
313     static void rmdir(const QString &path);
314     QString runExtDiff(const QString &workingDir, const QStringList &arguments, int timeOutS,
315                        QTextCodec *outputCodec = nullptr);
316     static QString getDriveLetterOfPath(const QString &directory);
317 
318     FileStatus::Status getFileStatus(const QString &fileName) const;
319     void updateStatusForFile(const QString &absFile);
320     void updateEditDerivedObjectWarning(const QString &fileName, const FileStatus::Status status);
321 
322     ClearCaseSettings m_settings;
323 
324     QString m_checkInMessageFileName;
325     QString m_checkInView;
326     QString m_topLevel;
327     QString m_stream;
328     ViewData m_viewData;
329     QString m_intStream;
330     QString m_activity;
331     QString m_diffPrefix;
332 
333     Core::CommandLocator *m_commandLocator = nullptr;
334     Utils::ParameterAction *m_checkOutAction = nullptr;
335     Utils::ParameterAction *m_checkInCurrentAction = nullptr;
336     Utils::ParameterAction *m_undoCheckOutAction = nullptr;
337     Utils::ParameterAction *m_undoHijackAction = nullptr;
338     Utils::ParameterAction *m_diffCurrentAction = nullptr;
339     Utils::ParameterAction *m_historyCurrentAction = nullptr;
340     Utils::ParameterAction *m_annotateCurrentAction = nullptr;
341     Utils::ParameterAction *m_addFileAction = nullptr;
342     QAction *m_diffActivityAction = nullptr;
343     QAction *m_updateIndexAction = nullptr;
344     Utils::ParameterAction *m_updateViewAction = nullptr;
345     Utils::ParameterAction *m_checkInActivityAction = nullptr;
346     QAction *m_checkInAllAction = nullptr;
347     QAction *m_statusAction = nullptr;
348 
349     QAction *m_menuAction = nullptr;
350     bool m_submitActionTriggered = false;
351     QMutex m_activityMutex;
352     QList<QStringPair> m_activities;
353     QSharedPointer<StatusMap> m_statusMap;
354 
355     ClearCaseSettingsPage m_settingsPage;
356 
357     VcsSubmitEditorFactory m_submitEditorFactory {
358         submitParameters,
__anon4e45f4a10202null359         [] { return new ClearCaseSubmitEditor; },
360         this
361     };
362 
363     VcsEditorFactory logEditorFactory {
364         &logEditorParameters,
__anon4e45f4a10302null365         [] { return new ClearCaseEditorWidget; },
366         std::bind(&ClearCasePluginPrivate::vcsDescribe, this, _1, _2)
367     };
368 
369     VcsEditorFactory annotateEditorFactory {
370         &annotateEditorParameters,
__anon4e45f4a10402null371         [] { return new ClearCaseEditorWidget; },
372         std::bind(&ClearCasePluginPrivate::vcsDescribe, this, _1, _2)
373     };
374 
375     VcsEditorFactory diffEditorFactory {
376         &diffEditorParameters,
__anon4e45f4a10502null377         [] { return new ClearCaseEditorWidget; },
378         std::bind(&ClearCasePluginPrivate::vcsDescribe, this, _1, _2)
379     };
380 
381     friend class ClearCasePlugin;
382 #ifdef WITH_TESTS
383     bool m_fakeClearTool = false;
384     QString m_tempFile;
385 #endif
386 };
387 
388 // ------------- ClearCasePlugin
389 static ClearCasePluginPrivate *dd = nullptr;
390 
~ClearCasePluginPrivate()391 ClearCasePluginPrivate::~ClearCasePluginPrivate()
392 {
393     cleanCheckInMessageFile();
394     // wait for sync thread to finish reading activities
395     QMutexLocker locker(&m_activityMutex);
396 }
397 
cleanCheckInMessageFile()398 void ClearCasePluginPrivate::cleanCheckInMessageFile()
399 {
400     if (!m_checkInMessageFileName.isEmpty()) {
401         QFile::remove(m_checkInMessageFileName);
402         m_checkInMessageFileName.clear();
403         m_checkInView.clear();
404     }
405 }
406 
isCheckInEditorOpen() const407 bool ClearCasePluginPrivate::isCheckInEditorOpen() const
408 {
409     return !m_checkInMessageFileName.isEmpty();
410 }
411 
412 /// Files in this directories are under ClearCase control
getVobList() const413 QStringList ClearCasePluginPrivate::getVobList() const
414 {
415     QStringList args(QLatin1String("lsvob"));
416     args << QLatin1String("-s");
417     const ClearCaseResponse response =
418             runCleartool(currentState().topLevel(), args, m_settings.timeOutS, SilentRun);
419 
420     return response.stdOut.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
421 }
422 
423 /// Get the drive letter of a path
424 /// Necessary since QDir(directory).rootPath() returns C:/ in all cases
getDriveLetterOfPath(const QString & directory)425 QString ClearCasePluginPrivate::getDriveLetterOfPath(const QString &directory)
426 {
427     // cdUp until we get just the drive letter
428     QDir dir(directory);
429     while (!dir.isRoot() && dir.cdUp())
430     { }
431 
432     return dir.path();
433 }
434 
updateStatusForFile(const QString & absFile)435 void ClearCasePluginPrivate::updateStatusForFile(const QString &absFile)
436 {
437     setStatus(absFile, getFileStatus(absFile), false);
438 }
439 
440 /// Give warning if a derived object is edited
updateEditDerivedObjectWarning(const QString & fileName,const FileStatus::Status status)441 void ClearCasePluginPrivate::updateEditDerivedObjectWarning(const QString &fileName,
442                                                      const FileStatus::Status status)
443 {
444     if (!isDynamic())
445         return;
446 
447     IDocument *curDocument = EditorManager::currentDocument();
448     if (!curDocument)
449         return;
450 
451     InfoBar *infoBar = curDocument->infoBar();
452     const Id derivedObjectWarning("ClearCase.DerivedObjectWarning");
453 
454     if (status == FileStatus::Derived) {
455         if (!infoBar->canInfoBeAdded(derivedObjectWarning))
456             return;
457 
458         infoBar->addInfo(InfoBarEntry(derivedObjectWarning,
459                                       tr("Editing Derived Object: %1").arg(fileName)));
460     } else {
461         infoBar->removeInfo(derivedObjectWarning);
462     }
463 }
464 
getFileStatus(const QString & fileName) const465 FileStatus::Status ClearCasePluginPrivate::getFileStatus(const QString &fileName) const
466 {
467     QTC_CHECK(!fileName.isEmpty());
468 
469     const QDir viewRootDir = QFileInfo(fileName).dir();
470     const QString viewRoot = viewRootDir.path();
471 
472     QStringList args(QLatin1String("ls"));
473     args << fileName;
474     QString buffer = runCleartoolSync(viewRoot, args);
475 
476     const int atatpos = buffer.indexOf(QLatin1String("@@"));
477     if (atatpos != -1) { // probably a managed file
478         const QString absFile =
479                 viewRootDir.absoluteFilePath(
480                     QDir::fromNativeSeparators(buffer.left(atatpos)));
481         QTC_CHECK(QFileInfo::exists(absFile));
482         QTC_CHECK(!absFile.isEmpty());
483 
484         // "cleartool ls" of a derived object looks like this:
485         // /path/to/file/export/MyFile.h@@--11-13T19:52.266580
486         const QChar c = buffer.at(atatpos + 2);
487         const bool isDerivedObject = c != QLatin1Char('/') && c != QLatin1Char('\\');
488         if (isDerivedObject)
489             return FileStatus::Derived;
490 
491         // find first whitespace. anything before that is not interesting
492         const int wspos = buffer.indexOf(QRegularExpression("\\s"));
493         if (buffer.lastIndexOf(QLatin1String("CHECKEDOUT"), wspos) != -1)
494             return FileStatus::CheckedOut;
495         else
496             return FileStatus::CheckedIn;
497     } else {
498         QTC_CHECK(QFileInfo::exists(fileName));
499         QTC_CHECK(!fileName.isEmpty());
500         return FileStatus::NotManaged;
501     }
502 }
503 
504 ///
505 /// Check if the directory is managed by ClearCase.
506 ///
507 /// There are 6 cases to consider for accessing ClearCase views:
508 ///
509 /// 1) Windows: dynamic view under M:\<view_tag> (working dir view)
510 /// 2) Windows: dynamic view under Z:\ (similar to unix "set view" by using "subst" or "net use")
511 /// 3) Windows: snapshot view
512 /// 4) Unix: dynamic view under /view/<view_tag> (working dir view)
513 /// 5) Unix: dynamic view which are set view (transparent access in a shell process)
514 /// 6) Unix: snapshot view
515 ///
516 /// Note: the drive letters M: and Z: can be chosen by the user. /view is the "view-root"
517 ///       directory and is not configurable, while VOB names and mount points are configurable
518 ///       by the ClearCase admin.
519 ///
520 /// Note: All cases except #5 have a root directory, i.e., all files reside under a directory.
521 ///       For #5 files are "mounted" and access is transparent (e.g., under /vobs).
522 ///
523 /// For a view named "myview" and a VOB named "vobA" topLevels would be:
524 /// 1) M:/myview/vobA
525 /// 2) Z:/vobA
526 /// 3) c:/snapshots/myview/vobA
527 /// 4) /view/myview/vobs/vobA
528 /// 5) /vobs/vobA/
529 /// 6) /home/<username>/snapshots/myview/vobs/vobA
530 ///
531 /// Note: The VOB directory is used as toplevel although the directory one up could have been
532 ///       used on cases execpt 5. For case 5 it would have been /, which we don't want.
533 ///
534 /// "cleartool pwv" returns the values for "set view" and "working directory view", also for
535 /// snapshot views.
536 ///
537 /// \returns The ClearCase topLevel/VOB directory for this directory
ccManagesDirectory(const QString & directory) const538 QString ClearCasePluginPrivate::ccManagesDirectory(const QString &directory) const
539 {
540     QStringList args(QLatin1String("pwv"));
541     const ClearCaseResponse response =
542             runCleartool(directory, args, m_settings.timeOutS, SilentRun);
543 
544     if (response.error)
545         return QString();
546 
547     const QStringList result = response.stdOut.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
548     if (result.size() != 2)
549         return QString();
550 
551     const QByteArray workingDirPattern("Working directory view: ");
552     if (!result[0].startsWith(QLatin1String(workingDirPattern)))
553         return QString();
554     const QString workingDirectoryView = result[0].mid(workingDirPattern.size());
555 
556     const QByteArray setViewDirPattern("Set view: ");
557     if (!result[1].startsWith(QLatin1String(setViewDirPattern)))
558         return QString();
559     const QString setView = result[1].mid(setViewDirPattern.size());
560 
561     const QString none(QLatin1String("** NONE **"));
562     QString rootDir;
563     if (setView != none || workingDirectoryView != none)
564         rootDir = ccViewRoot(directory);
565     else
566         return QString();
567 
568     // Check if the directory is inside one of the known VOBs.
569     static QStringList vobs;
570     if (vobs.empty())
571         vobs = getVobList();
572 
573     foreach (const QString &relativeVobDir, vobs) {
574         const QString vobPath = QDir::cleanPath(rootDir + QDir::fromNativeSeparators(relativeVobDir));
575         const bool isManaged = (vobPath == directory)
576                 || FilePath::fromString(directory).isChildOf(FilePath::fromString(vobPath));
577         if (isManaged)
578             return vobPath;
579     }
580 
581     return QString();
582 }
583 
584 /// Find the root path of a clearcase view. Precondition: This is a clearcase managed dir
ccViewRoot(const QString & directory) const585 QString ClearCasePluginPrivate::ccViewRoot(const QString &directory) const
586 {
587     QStringList args(QLatin1String("pwv"));
588     args << QLatin1String("-root");
589     const ClearCaseResponse response =
590             runCleartool(directory, args, m_settings.timeOutS, SilentRun);
591 
592     QString root = response.stdOut.trimmed();
593 
594     if (root.isEmpty()) {
595         if (HostOsInfo::isWindowsHost())
596             root = getDriveLetterOfPath(directory);
597         else
598             root = QLatin1Char('/');
599     }
600 
601     return QDir::fromNativeSeparators(root);
602 }
603 
604 /*! Find top level for view that contains \a directory
605  *
606  * Handles both dynamic views and snapshot views.
607  */
findTopLevel(const QString & directory) const608 QString ClearCasePluginPrivate::findTopLevel(const QString &directory) const
609 {
610     // Do not check again if we've already tested that the dir is managed,
611     // or if it is a child of a managed dir (top level).
612     if ((directory == m_topLevel) ||
613            FilePath::fromString(directory).isChildOf(FilePath::fromString(m_topLevel)))
614         return m_topLevel;
615 
616     return ccManagesDirectory(directory);
617 }
618 
initialize(const QStringList &,QString * errorMessage)619 bool ClearCasePlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
620 {
621     Q_UNUSED(errorMessage)
622     dd = new ClearCasePluginPrivate;
623     return true;
624 }
625 
extensionsInitialized()626 void ClearCasePlugin::extensionsInitialized()
627 {
628     dd->extensionsInitialized();
629 }
630 
ClearCasePluginPrivate()631 ClearCasePluginPrivate::ClearCasePluginPrivate()
632     : VcsBase::VcsBasePluginPrivate(Context(CLEARCASE_CONTEXT)),
633       m_statusMap(new StatusMap)
634 {
635     dd = this;
636 
637     qRegisterMetaType<ClearCase::Internal::FileStatus::Status>("ClearCase::Internal::FileStatus::Status");
638     connect(qApp, &QApplication::applicationStateChanged,
639             this, [this](Qt::ApplicationState state) {
640                 if (state == Qt::ApplicationActive)
641                     syncSlot();
642             });
643 
644     using namespace Constants;
645     using namespace Core::Constants;
646 
647     Context context(CLEARCASE_CONTEXT);
648 
649     connect(ICore::instance(), &ICore::coreAboutToClose, this, &ClearCasePluginPrivate::closing);
650     connect(ProgressManager::instance(), &ProgressManager::allTasksFinished,
651             this, &ClearCasePluginPrivate::tasksFinished);
652 
653     m_settings.fromSettings(ICore::settings());
654 
655     // update view name when changing active project
656     connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
657             this, &ClearCasePluginPrivate::projectChanged);
658 
659     const QString description = QLatin1String("ClearCase");
660     const QString prefix = QLatin1String("cc");
661     // register cc prefix in Locator
662     m_commandLocator = new CommandLocator("cc", description, prefix, this);
663     m_commandLocator->setDescription(tr("Triggers a ClearCase version control operation."));
664 
665     //register actions
666     ActionContainer *toolsContainer = ActionManager::actionContainer(M_TOOLS);
667 
668     ActionContainer *clearcaseMenu = ActionManager::createMenu(CMD_ID_CLEARCASE_MENU);
669     clearcaseMenu->menu()->setTitle(tr("C&learCase"));
670     toolsContainer->addMenu(clearcaseMenu);
671     m_menuAction = clearcaseMenu->menu()->menuAction();
672     Command *command;
673 
674     m_checkOutAction = new ParameterAction(tr("Check Out..."), tr("Check &Out \"%1\"..."), ParameterAction::AlwaysEnabled, this);
675     command = ActionManager::registerAction(m_checkOutAction, CMD_ID_CHECKOUT,
676         context);
677     command->setAttribute(Command::CA_UpdateText);
678     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+O") : tr("Alt+L,Alt+O")));
679     connect(m_checkOutAction, &QAction::triggered, this, &ClearCasePluginPrivate::checkOutCurrentFile);
680     clearcaseMenu->addAction(command);
681     m_commandLocator->appendCommand(command);
682 
683     m_checkInCurrentAction = new ParameterAction(tr("Check &In..."), tr("Check &In \"%1\"..."), ParameterAction::AlwaysEnabled, this);
684     command = ActionManager::registerAction(m_checkInCurrentAction, CMD_ID_CHECKIN, context);
685     command->setAttribute(Command::CA_UpdateText);
686     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+I") : tr("Alt+L,Alt+I")));
687     connect(m_checkInCurrentAction, &QAction::triggered, this, &ClearCasePluginPrivate::startCheckInCurrentFile);
688     clearcaseMenu->addAction(command);
689     m_commandLocator->appendCommand(command);
690 
691     m_undoCheckOutAction = new ParameterAction(tr("Undo Check Out"), tr("&Undo Check Out \"%1\""), ParameterAction::AlwaysEnabled, this);
692     command = ActionManager::registerAction(m_undoCheckOutAction, CMD_ID_UNDOCHECKOUT, context);
693     command->setAttribute(Command::CA_UpdateText);
694     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+U") : tr("Alt+L,Alt+U")));
695     connect(m_undoCheckOutAction, &QAction::triggered, this, &ClearCasePluginPrivate::undoCheckOutCurrent);
696     clearcaseMenu->addAction(command);
697     m_commandLocator->appendCommand(command);
698 
699     m_undoHijackAction = new ParameterAction(tr("Undo Hijack"), tr("Undo Hi&jack \"%1\""), ParameterAction::AlwaysEnabled, this);
700     command = ActionManager::registerAction(m_undoHijackAction, CMD_ID_UNDOHIJACK, context);
701     command->setAttribute(Command::CA_UpdateText);
702     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+R") : tr("Alt+L,Alt+R")));
703     connect(m_undoHijackAction, &QAction::triggered, this, &ClearCasePluginPrivate::undoHijackCurrent);
704     clearcaseMenu->addAction(command);
705     m_commandLocator->appendCommand(command);
706 
707     clearcaseMenu->addSeparator(context);
708 
709     m_diffCurrentAction = new ParameterAction(tr("Diff Current File"), tr("&Diff \"%1\""), ParameterAction::EnabledWithParameter, this);
710     command = ActionManager::registerAction(m_diffCurrentAction,
711         CMD_ID_DIFF_CURRENT, context);
712     command->setAttribute(Command::CA_UpdateText);
713     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+D") : tr("Alt+L,Alt+D")));
714     connect(m_diffCurrentAction, &QAction::triggered, this, &ClearCasePluginPrivate::diffCurrentFile);
715     clearcaseMenu->addAction(command);
716     m_commandLocator->appendCommand(command);
717 
718     m_historyCurrentAction = new ParameterAction(tr("History Current File"), tr("&History \"%1\""), ParameterAction::EnabledWithParameter, this);
719     command = ActionManager::registerAction(m_historyCurrentAction,
720         CMD_ID_HISTORY_CURRENT, context);
721     command->setAttribute(Command::CA_UpdateText);
722     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+H") : tr("Alt+L,Alt+H")));
723     connect(m_historyCurrentAction, &QAction::triggered, this,
724         &ClearCasePluginPrivate::historyCurrentFile);
725     clearcaseMenu->addAction(command);
726     m_commandLocator->appendCommand(command);
727 
728     m_annotateCurrentAction = new ParameterAction(tr("Annotate Current File"), tr("&Annotate \"%1\""), ParameterAction::EnabledWithParameter, this);
729     command = ActionManager::registerAction(m_annotateCurrentAction,
730         CMD_ID_ANNOTATE, context);
731     command->setAttribute(Command::CA_UpdateText);
732     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+A") : tr("Alt+L,Alt+A")));
733     connect(m_annotateCurrentAction, &QAction::triggered, this,
734         &ClearCasePluginPrivate::annotateCurrentFile);
735     clearcaseMenu->addAction(command);
736     m_commandLocator->appendCommand(command);
737 
738     m_addFileAction = new ParameterAction(tr("Add File..."), tr("Add File \"%1\""), ParameterAction::EnabledWithParameter, this);
739     command = ActionManager::registerAction(m_addFileAction, CMD_ID_ADD_FILE, context);
740     command->setAttribute(Command::CA_UpdateText);
741     connect(m_addFileAction, &QAction::triggered, this, &ClearCasePluginPrivate::addCurrentFile);
742     clearcaseMenu->addAction(command);
743 
744     clearcaseMenu->addSeparator(context);
745 
746     m_diffActivityAction = new QAction(tr("Diff A&ctivity..."), this);
747     m_diffActivityAction->setEnabled(false);
748     command = ActionManager::registerAction(m_diffActivityAction, CMD_ID_DIFF_ACTIVITY, context);
749     connect(m_diffActivityAction, &QAction::triggered, this, &ClearCasePluginPrivate::diffActivity);
750     clearcaseMenu->addAction(command);
751     m_commandLocator->appendCommand(command);
752 
753     m_checkInActivityAction = new ParameterAction(tr("Ch&eck In Activity"), tr("Chec&k In Activity \"%1\"..."), ParameterAction::EnabledWithParameter, this);
754     m_checkInActivityAction->setEnabled(false);
755     command = ActionManager::registerAction(m_checkInActivityAction, CMD_ID_CHECKIN_ACTIVITY, context);
756     connect(m_checkInActivityAction, &QAction::triggered, this, &ClearCasePluginPrivate::startCheckInActivity);
757     command->setAttribute(Command::CA_UpdateText);
758     clearcaseMenu->addAction(command);
759     m_commandLocator->appendCommand(command);
760 
761     clearcaseMenu->addSeparator(context);
762 
763     m_updateIndexAction = new QAction(tr("Update Index"), this);
764     command = ActionManager::registerAction(m_updateIndexAction, CMD_ID_UPDATEINDEX, context);
765     connect(m_updateIndexAction, &QAction::triggered, this, &ClearCasePluginPrivate::updateIndex);
766     clearcaseMenu->addAction(command);
767 
768     m_updateViewAction = new ParameterAction(tr("Update View"), tr("U&pdate View \"%1\""), ParameterAction::EnabledWithParameter, this);
769     command = ActionManager::registerAction(m_updateViewAction, CMD_ID_UPDATE_VIEW, context);
770     connect(m_updateViewAction, &QAction::triggered, this, &ClearCasePluginPrivate::updateView);
771     command->setAttribute(Command::CA_UpdateText);
772     clearcaseMenu->addAction(command);
773 
774     clearcaseMenu->addSeparator(context);
775 
776     m_checkInAllAction = new QAction(tr("Check In All &Files..."), this);
777     command = ActionManager::registerAction(m_checkInAllAction, CMD_ID_CHECKIN_ALL, context);
778     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+F") : tr("Alt+L,Alt+F")));
779     connect(m_checkInAllAction, &QAction::triggered, this, &ClearCasePluginPrivate::startCheckInAll);
780     clearcaseMenu->addAction(command);
781     m_commandLocator->appendCommand(command);
782 
783     m_statusAction = new QAction(tr("View &Status"), this);
784     command = ActionManager::registerAction(m_statusAction, CMD_ID_STATUS, context);
785     command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+L,Meta+S") : tr("Alt+L,Alt+S")));
786     connect(m_statusAction, &QAction::triggered, this, &ClearCasePluginPrivate::viewStatus);
787     clearcaseMenu->addAction(command);
788     m_commandLocator->appendCommand(command);
789 }
790 
791 // called before closing the submit editor
submitEditorAboutToClose()792 bool ClearCasePluginPrivate::submitEditorAboutToClose()
793 {
794     if (!isCheckInEditorOpen())
795         return true;
796 
797     auto editor = qobject_cast<ClearCaseSubmitEditor *>(submitEditor());
798     QTC_ASSERT(editor, return true);
799     IDocument *editorDocument = editor->document();
800     QTC_ASSERT(editorDocument, return true);
801 
802     // Submit editor closing. Make it write out the check in message
803     // and retrieve files
804     const QFileInfo editorFile = editorDocument->filePath().toFileInfo();
805     const QFileInfo changeFile(m_checkInMessageFileName);
806     if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
807         return true; // Oops?!
808 
809     // Prompt user. Force a prompt unless submit was actually invoked (that
810     // is, the editor was closed or shutdown).
811     bool prompt = m_settings.promptToCheckIn;
812     const VcsBaseSubmitEditor::PromptSubmitResult answer =
813             editor->promptSubmit(this, &prompt, !m_submitActionTriggered);
814     m_submitActionTriggered = false;
815     switch (answer) {
816     case VcsBaseSubmitEditor::SubmitCanceled:
817         return false; // Keep editing and change file
818     case VcsBaseSubmitEditor::SubmitDiscarded:
819         cleanCheckInMessageFile();
820         return true; // Cancel all
821     default:
822         break;
823     }
824     // If user changed
825     if (prompt != m_settings.promptToCheckIn) {
826         m_settings.promptToCheckIn = prompt;
827         m_settings.toSettings(ICore::settings());
828     }
829 
830     const QStringList fileList = editor->checkedFiles();
831     bool closeEditor = true;
832     if (!fileList.empty()) {
833         // get message & check in
834         closeEditor = DocumentManager::saveDocument(editorDocument);
835         if (closeEditor) {
836             ClearCaseSubmitEditorWidget *widget = editor->submitEditorWidget();
837             closeEditor = vcsCheckIn(m_checkInMessageFileName, fileList, widget->activity(),
838                                    widget->isIdentical(), widget->isPreserve(),
839                                    widget->activityChanged());
840         }
841     }
842     // vcsCheckIn might fail if some of the files failed to check-in (though it does check-in
843     // those who didn't fail). Therefore, if more than one file was sent, consider it as success
844     // anyway (sync will be called from vcsCheckIn for next attempt)
845     closeEditor |= (fileList.count() > 1);
846     if (closeEditor)
847         cleanCheckInMessageFile();
848     return closeEditor;
849 }
850 
diffCheckInFiles(const QStringList & files)851 void ClearCasePluginPrivate::diffCheckInFiles(const QStringList &files)
852 {
853     ccDiffWithPred(m_checkInView, files);
854 }
855 
setWorkingDirectory(IEditor * editor,const QString & wd)856 static void setWorkingDirectory(IEditor *editor, const QString &wd)
857 {
858     if (auto ve = qobject_cast<VcsBaseEditorWidget*>(editor->widget()))
859         ve->setWorkingDirectory(wd);
860 }
861 
862 //! retrieve full location of predecessor of \a version
ccGetPredecessor(const QString & version) const863 QString ClearCasePluginPrivate::ccGetPredecessor(const QString &version) const
864 {
865     QStringList args(QLatin1String("describe"));
866     args << QLatin1String("-fmt") << QLatin1String("%En@@%PSn") << version;
867     const ClearCaseResponse response =
868             runCleartool(currentState().topLevel(), args, m_settings.timeOutS, SilentRun);
869     if (response.error || response.stdOut.endsWith(QLatin1Char('@'))) // <name-unknown>@@
870         return QString();
871     else
872         return response.stdOut;
873 }
874 
875 //! Get a list of paths to active VOBs.
876 //! Paths are relative to viewRoot
ccGetActiveVobs() const877 QStringList ClearCasePluginPrivate::ccGetActiveVobs() const
878 {
879     QStringList res;
880     QStringList args(QLatin1String("lsvob"));
881     const QString theViewRoot = viewRoot();
882 
883     const ClearCaseResponse response =
884             runCleartool(theViewRoot, args, m_settings.timeOutS, SilentRun);
885     if (response.error)
886         return res;
887 
888     // format of output unix:
889     // * /path/to/vob   /path/to/vob/storage.vbs <and some text omitted here>
890     // format of output windows:
891     // * \vob     \\share\path\to\vob\storage.vbs <and some text omitted here>
892     QString prefix = theViewRoot;
893     if (!prefix.endsWith(QLatin1Char('/')))
894         prefix += QLatin1Char('/');
895 
896     const QDir theViewRootDir(theViewRoot);
897     foreach (const QString &line, response.stdOut.split(QLatin1Char('\n'), Qt::SkipEmptyParts)) {
898         const bool isActive = line.at(0) == QLatin1Char('*');
899         if (!isActive)
900             continue;
901 
902         const QString dir =
903                 QDir::fromNativeSeparators(line.mid(3, line.indexOf(QLatin1Char(' '), 3) - 3));
904         const QString relativeDir = theViewRootDir.relativeFilePath(dir);
905 
906         // Snapshot views does not necessarily have all active VOBs loaded, so we'll have to
907         // check if the dirs exists as well. Else the command will work, but the output will
908         // complain about the element not being loaded.
909         if (QFile::exists(prefix + relativeDir))
910             res.append(relativeDir);
911     }
912     return res;
913 }
914 
checkAndReIndexUnknownFile(const QString & file)915 void ClearCasePluginPrivate::checkAndReIndexUnknownFile(const QString &file)
916 {
917     if (isDynamic()) {
918         // reindex unknown files
919         if (m_statusMap->value(file, FileStatus(FileStatus::Unknown)).status == FileStatus::Unknown)
920             updateStatusForFile(file);
921     }
922 }
923 
924 // file must be absolute, and using '/' path separator
vcsStatus(const QString & file) const925 FileStatus ClearCasePluginPrivate::vcsStatus(const QString &file) const
926 {
927     return m_statusMap->value(file, FileStatus(FileStatus::Unknown));
928 }
929 
ccGetFileActivity(const QString & workingDir,const QString & file)930 QString ClearCasePluginPrivate::ccGetFileActivity(const QString &workingDir, const QString &file)
931 {
932     QStringList args(QLatin1String("lscheckout"));
933     args << QLatin1String("-fmt") << QLatin1String("%[activity]p");
934     args << file;
935     const ClearCaseResponse response =
936             runCleartool(workingDir, args, m_settings.timeOutS, SilentRun);
937     return response.stdOut;
938 }
939 
openClearCaseSubmitEditor(const QString & fileName,bool isUcm)940 ClearCaseSubmitEditor *ClearCasePluginPrivate::openClearCaseSubmitEditor(const QString &fileName, bool isUcm)
941 {
942     IEditor *editor =
943             EditorManager::openEditor(fileName, Constants::CLEARCASECHECKINEDITOR_ID);
944     auto submitEditor = qobject_cast<ClearCaseSubmitEditor*>(editor);
945     QTC_ASSERT(submitEditor, return nullptr);
946     connect(submitEditor, &VcsBaseSubmitEditor::diffSelectedFiles,
947             this, &ClearCasePluginPrivate::diffCheckInFiles);
948     submitEditor->setCheckScriptWorkingDirectory(m_checkInView);
949     submitEditor->setIsUcm(isUcm);
950     return submitEditor;
951 }
952 
fileStatusToText(FileStatus fileStatus)953 QString fileStatusToText(FileStatus fileStatus)
954 {
955     switch (fileStatus.status)
956     {
957     case FileStatus::CheckedIn:
958         return QLatin1String("CheckedIn");
959     case FileStatus::CheckedOut:
960         return QLatin1String("CheckedOut");
961     case FileStatus::Hijacked:
962         return QLatin1String("Hijacked");
963     case FileStatus::Missing:
964         return QLatin1String("Missing");
965     case FileStatus::NotManaged:
966         return QLatin1String("ViewPrivate");
967     case FileStatus::Unknown:
968         return QLatin1String("Unknown");
969     default:
970         return QLatin1String("default");
971     }
972 }
973 
updateStatusActions()974 void ClearCasePluginPrivate::updateStatusActions()
975 {
976     FileStatus fileStatus = FileStatus::Unknown;
977     bool hasFile = currentState().hasFile();
978     if (hasFile) {
979         QString absoluteFileName = currentState().currentFile();
980         checkAndReIndexUnknownFile(absoluteFileName);
981         fileStatus = vcsStatus(absoluteFileName);
982 
983         updateEditDerivedObjectWarning(absoluteFileName, fileStatus.status);
984 
985         if (Constants::debug)
986             qDebug() << Q_FUNC_INFO << absoluteFileName << ", status = "
987                      << fileStatusToText(fileStatus.status) << "(" << fileStatus.status << ")";
988     }
989 
990     m_checkOutAction->setEnabled(hasFile && (fileStatus.status & (FileStatus::CheckedIn | FileStatus::Hijacked)));
991     m_undoCheckOutAction->setEnabled(hasFile && (fileStatus.status & FileStatus::CheckedOut));
992     m_undoHijackAction->setEnabled(!m_viewData.isDynamic && hasFile && (fileStatus.status & FileStatus::Hijacked));
993     m_checkInCurrentAction->setEnabled(hasFile && (fileStatus.status & FileStatus::CheckedOut));
994     m_addFileAction->setEnabled(hasFile && (fileStatus.status & FileStatus::NotManaged));
995     m_diffCurrentAction->setEnabled(hasFile && (fileStatus.status != FileStatus::NotManaged));
996     m_historyCurrentAction->setEnabled(hasFile && (fileStatus.status != FileStatus::NotManaged));
997     m_annotateCurrentAction->setEnabled(hasFile && (fileStatus.status != FileStatus::NotManaged));
998 
999     m_checkInActivityAction->setEnabled(m_viewData.isUcm);
1000     m_diffActivityAction->setEnabled(m_viewData.isUcm);
1001 }
1002 
updateActions(VcsBasePluginPrivate::ActionState as)1003 void ClearCasePluginPrivate::updateActions(VcsBasePluginPrivate::ActionState as)
1004 {
1005     if (!enableMenuAction(as, m_menuAction)) {
1006         m_commandLocator->setEnabled(false);
1007         return;
1008     }
1009     const VcsBasePluginState state = currentState();
1010     const bool hasTopLevel = state.hasTopLevel();
1011     m_commandLocator->setEnabled(hasTopLevel);
1012     if (hasTopLevel) {
1013         const QString topLevel = state.topLevel();
1014         if (m_topLevel != topLevel) {
1015             m_topLevel = topLevel;
1016             m_viewData = ccGetView(topLevel);
1017         }
1018     }
1019 
1020     m_updateViewAction->setParameter(m_viewData.isDynamic ? QString() : m_viewData.name);
1021 
1022     const QString fileName = state.currentFileName();
1023     m_checkOutAction->setParameter(fileName);
1024     m_undoCheckOutAction->setParameter(fileName);
1025     m_undoHijackAction->setParameter(fileName);
1026     m_diffCurrentAction->setParameter(fileName);
1027     m_checkInCurrentAction->setParameter(fileName);
1028     m_historyCurrentAction->setParameter(fileName);
1029     m_annotateCurrentAction->setParameter(fileName);
1030     m_addFileAction->setParameter(fileName);
1031     m_updateIndexAction->setEnabled(!m_settings.disableIndexer);
1032 
1033     updateStatusActions();
1034 }
1035 
commitDisplayName() const1036 QString ClearCasePluginPrivate::commitDisplayName() const
1037 {
1038     return tr("Check In");
1039 }
1040 
checkOutCurrentFile()1041 void ClearCasePluginPrivate::checkOutCurrentFile()
1042 {
1043     const VcsBasePluginState state = currentState();
1044     QTC_ASSERT(state.hasFile(), return);
1045     vcsOpen(state.currentFileTopLevel(), state.relativeCurrentFile());
1046 }
1047 
addCurrentFile()1048 void ClearCasePluginPrivate::addCurrentFile()
1049 {
1050     const VcsBasePluginState state = currentState();
1051     QTC_ASSERT(state.hasFile(), return);
1052     vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
1053 }
1054 
1055 // Set the FileStatus of file given in absolute path
setStatus(const QString & file,FileStatus::Status status,bool update)1056 void ClearCasePluginPrivate::setStatus(const QString &file, FileStatus::Status status, bool update)
1057 {
1058     QTC_CHECK(!file.isEmpty());
1059     m_statusMap->insert(file, FileStatus(status, QFileInfo(file).permissions()));
1060 
1061     if (update && currentState().currentFile() == file)
1062         QMetaObject::invokeMethod(this, &ClearCasePluginPrivate::updateStatusActions);
1063 }
1064 
undoCheckOutCurrent()1065 void ClearCasePluginPrivate::undoCheckOutCurrent()
1066 {
1067     const VcsBasePluginState state = currentState();
1068     QTC_ASSERT(state.hasFile(), return);
1069     QString file = state.relativeCurrentFile();
1070     const QString fileName = QDir::toNativeSeparators(file);
1071 
1072     QStringList args(QLatin1String("diff"));
1073     args << QLatin1String("-diff_format") << QLatin1String("-predecessor");
1074     args << fileName;
1075 
1076     const ClearCaseResponse diffResponse =
1077             runCleartool(state.currentFileTopLevel(), args, m_settings.timeOutS, 0);
1078 
1079     bool different = diffResponse.error; // return value is 1 if there is any difference
1080     bool keep = false;
1081     if (different) {
1082         Ui::UndoCheckOut uncoUi;
1083         QDialog uncoDlg;
1084         uncoUi.setupUi(&uncoDlg);
1085         uncoUi.lblMessage->setText(tr("Do you want to undo the check out of \"%1\"?").arg(fileName));
1086         uncoUi.chkKeep->setChecked(m_settings.keepFileUndoCheckout);
1087         if (uncoDlg.exec() != QDialog::Accepted)
1088             return;
1089         keep = uncoUi.chkKeep->isChecked();
1090         if (keep != m_settings.keepFileUndoCheckout) {
1091             m_settings.keepFileUndoCheckout = keep;
1092             m_settings.toSettings(ICore::settings());
1093         }
1094     }
1095     vcsUndoCheckOut(state.topLevel(), file, keep);
1096 }
1097 
vcsUndoCheckOut(const QString & workingDir,const QString & fileName,bool keep)1098 bool ClearCasePluginPrivate::vcsUndoCheckOut(const QString &workingDir, const QString &fileName, bool keep)
1099 {
1100     if (Constants::debug)
1101         qDebug() << Q_FUNC_INFO << workingDir << fileName << keep;
1102 
1103     FileChangeBlocker fcb(FilePath::fromString(fileName));
1104 
1105     // revert
1106     QStringList args(QLatin1String("uncheckout"));
1107     args << QLatin1String(keep ? "-keep" : "-rm");
1108     args << QDir::toNativeSeparators(fileName);
1109 
1110     const ClearCaseResponse response =
1111             runCleartool(workingDir, args, m_settings.timeOutS,
1112                          VcsCommand::ShowStdOut | VcsCommand::FullySynchronously);
1113 
1114     if (!response.error) {
1115         const QString absPath = workingDir + QLatin1Char('/') + fileName;
1116 
1117         if (!m_settings.disableIndexer)
1118             setStatus(absPath, FileStatus::CheckedIn);
1119         emit filesChanged(QStringList(absPath));
1120     }
1121     return !response.error;
1122 }
1123 
1124 
1125 /*! Undo a hijacked file in a snapshot view
1126  *
1127  * Runs cleartool update -overwrite \a fileName in \a workingDir
1128  * if \a keep is true, renames hijacked files to <filename>.keep. Otherwise it is overwritten
1129  */
vcsUndoHijack(const QString & workingDir,const QString & fileName,bool keep)1130 bool ClearCasePluginPrivate::vcsUndoHijack(const QString &workingDir, const QString &fileName, bool keep)
1131 {
1132     if (Constants::debug)
1133         qDebug() << Q_FUNC_INFO << workingDir << fileName << keep;
1134     QStringList args(QLatin1String("update"));
1135     args << QLatin1String(keep ? "-rename" : "-overwrite");
1136     args << QLatin1String("-log");
1137     if (HostOsInfo::isWindowsHost())
1138         args << QLatin1String("NUL");
1139     else
1140     args << QLatin1String("/dev/null");
1141     args << QDir::toNativeSeparators(fileName);
1142 
1143     const ClearCaseResponse response =
1144             runCleartool(workingDir, args, m_settings.timeOutS,
1145                    VcsCommand::ShowStdOut | VcsCommand::FullySynchronously);
1146     if (!response.error && !m_settings.disableIndexer) {
1147         const QString absPath = workingDir + QLatin1Char('/') + fileName;
1148         setStatus(absPath, FileStatus::CheckedIn);
1149     }
1150     return !response.error;
1151 }
1152 
undoHijackCurrent()1153 void ClearCasePluginPrivate::undoHijackCurrent()
1154 {
1155     const VcsBasePluginState state = currentState();
1156     QTC_ASSERT(state.hasFile(), return);
1157     const QString fileName = state.relativeCurrentFile();
1158 
1159     bool keep = false;
1160     bool askKeep = true;
1161     if (m_settings.extDiffAvailable) {
1162         QString diffres = diffExternal(ccGetFileVersion(state.topLevel(), fileName), fileName);
1163         if (diffres.at(0) == QLatin1Char('F')) // Files are identical
1164             askKeep = false;
1165     }
1166     if (askKeep) {
1167         Ui::UndoCheckOut unhijackUi;
1168         QDialog unhijackDlg;
1169         unhijackUi.setupUi(&unhijackDlg);
1170         unhijackDlg.setWindowTitle(tr("Undo Hijack File"));
1171         unhijackUi.lblMessage->setText(tr("Do you want to undo hijack of \"%1\"?")
1172                                        .arg(QDir::toNativeSeparators(fileName)));
1173         if (unhijackDlg.exec() != QDialog::Accepted)
1174             return;
1175         keep = unhijackUi.chkKeep->isChecked();
1176     }
1177 
1178     FileChangeBlocker fcb(FilePath::fromString(state.currentFile()));
1179 
1180     // revert
1181     if (vcsUndoHijack(state.currentFileTopLevel(), fileName, keep))
1182         emit filesChanged(QStringList(state.currentFile()));
1183 }
1184 
ccGetFileVersion(const QString & workingDir,const QString & file) const1185 QString ClearCasePluginPrivate::ccGetFileVersion(const QString &workingDir, const QString &file) const
1186 {
1187     QStringList args(QLatin1String("ls"));
1188     args << QLatin1String("-short") << file;
1189     return runCleartoolSync(workingDir, args).trimmed();
1190 }
1191 
ccDiffWithPred(const QString & workingDir,const QStringList & files)1192 void ClearCasePluginPrivate::ccDiffWithPred(const QString &workingDir, const QStringList &files)
1193 {
1194     if (Constants::debug)
1195         qDebug() << Q_FUNC_INFO << files;
1196     const QString source = VcsBaseEditor::getSource(workingDir, files);
1197     QTextCodec *codec = source.isEmpty() ? static_cast<QTextCodec *>(nullptr)
1198                                          : VcsBaseEditor::getCodec(source);
1199 
1200     if ((m_settings.diffType == GraphicalDiff) && (files.count() == 1)) {
1201         const QString file = files.first();
1202         const QString absFilePath = workingDir + QLatin1Char('/') + file;
1203         if (vcsStatus(absFilePath).status == FileStatus::Hijacked)
1204             diffGraphical(ccGetFileVersion(workingDir, file), file);
1205         else
1206             diffGraphical(file);
1207         return; // done here, diff is opened in a new window
1208     }
1209     if (!m_settings.extDiffAvailable) {
1210         VcsOutputWindow::appendError(tr("External diff is required to compare multiple files."));
1211         return;
1212     }
1213     QString result;
1214     foreach (const QString &file, files) {
1215         const QString absFilePath = workingDir + QLatin1Char('/') + file;
1216         if (vcsStatus(QDir::fromNativeSeparators(absFilePath)).status == FileStatus::Hijacked)
1217             result += diffExternal(ccGetFileVersion(workingDir, file), file);
1218         else
1219             result += diffExternal(file);
1220     }
1221 
1222     QString diffname;
1223 
1224     // diff of a single file? re-use an existing view if possible to support
1225     // the common usage pattern of continuously changing and diffing a file
1226     const QString tag = VcsBaseEditor::editorTag(DiffOutput, workingDir, files);
1227     if (files.count() == 1) {
1228         // Show in the same editor if diff has been executed before
1229         if (IEditor *existingEditor = VcsBaseEditor::locateEditorByTag(tag)) {
1230             existingEditor->document()->setContents(result.toUtf8());
1231             EditorManager::activateEditor(existingEditor);
1232             setWorkingDirectory(existingEditor, workingDir);
1233             return;
1234         }
1235         diffname = QDir::toNativeSeparators(files.first());
1236     }
1237     const QString title = QString::fromLatin1("cc diff %1").arg(diffname);
1238     IEditor *editor = showOutputInEditor(title, result, diffEditorParameters.id, source, codec);
1239     setWorkingDirectory(editor, workingDir);
1240     VcsBaseEditor::tagEditor(editor, tag);
1241     auto diffEditorWidget = qobject_cast<ClearCaseEditorWidget *>(editor->widget());
1242     QTC_ASSERT(diffEditorWidget, return);
1243     if (files.count() == 1)
1244         editor->setProperty("originalFileName", diffname);
1245 }
1246 
ccGetActivityVersions(const QString & workingDir,const QString & activity)1247 QStringList ClearCasePluginPrivate::ccGetActivityVersions(const QString &workingDir, const QString &activity)
1248 {
1249     QStringList args(QLatin1String("lsactivity"));
1250     args << QLatin1String("-fmt") << QLatin1String("%[versions]Cp") << activity;
1251     const ClearCaseResponse response =
1252         runCleartool(workingDir, args, m_settings.timeOutS, SilentRun);
1253     if (response.error)
1254         return QStringList();
1255     QStringList versions = response.stdOut.split(QLatin1String(", "));
1256     versions.sort();
1257     return versions;
1258 }
1259 
rmdir(const QString & path)1260 void ClearCasePluginPrivate::rmdir(const QString &path)
1261 {
1262     QDir dir(path);
1263     foreach (QFileInfo fi, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
1264         if (fi.isDir()) {
1265             rmdir(fi.canonicalFilePath());
1266             dir.rmdir(fi.baseName());
1267         }
1268         else
1269             QFile::remove(fi.canonicalFilePath());
1270     }
1271 }
1272 
diffActivity()1273 void ClearCasePluginPrivate::diffActivity()
1274 {
1275     using FileVerIt = QMap<QString, QStringPair>::Iterator;
1276 
1277     const VcsBasePluginState state = currentState();
1278     QTC_ASSERT(state.hasTopLevel(), return);
1279     if (Constants::debug)
1280         qDebug() << Q_FUNC_INFO;
1281     if (!m_settings.extDiffAvailable) {
1282         VcsOutputWindow::appendError(tr("External diff is required to compare multiple files."));
1283         return;
1284     }
1285     QString topLevel = state.topLevel();
1286     QString activity = QInputDialog::getText(ICore::dialogParent(), tr("Enter Activity"),
1287                                              tr("Activity Name"), QLineEdit::Normal, m_activity);
1288     if (activity.isEmpty())
1289         return;
1290     QStringList versions = ccGetActivityVersions(topLevel, activity);
1291 
1292     QString result;
1293     // map from fileName to (first, latest) pair
1294     QMap<QString, QStringPair> filever;
1295     int topLevelLen = topLevel.length();
1296     foreach (const QString &version, versions) {
1297         QString shortver = version.mid(topLevelLen + 1);
1298         int atatpos = shortver.indexOf(QLatin1String("@@"));
1299         if (atatpos != -1) {
1300             QString file = shortver.left(atatpos);
1301             // latest version - updated each line
1302             filever[file].second = shortver;
1303 
1304             // pre-first version. only for the first occurrence
1305             if (filever[file].first.isEmpty()) {
1306                 int verpos = shortver.lastIndexOf(QRegularExpression("[^0-9]")) + 1;
1307                 int vernum = shortver.mid(verpos).toInt();
1308                 if (vernum)
1309                     --vernum;
1310                 shortver.replace(verpos, shortver.length() - verpos, QString::number(vernum));
1311                 // first version
1312                 filever[file].first = shortver;
1313             }
1314         }
1315     }
1316 
1317     if ((m_settings.diffType == GraphicalDiff) && (filever.count() == 1)) {
1318         QStringPair pair(filever.first());
1319         diffGraphical(pair.first, pair.second);
1320         return;
1321     }
1322     rmdir(Utils::TemporaryDirectory::masterDirectoryPath() + QLatin1String("/ccdiff/") + activity);
1323     QDir(Utils::TemporaryDirectory::masterDirectoryPath()).rmpath(QLatin1String("ccdiff/") + activity);
1324     m_diffPrefix = activity;
1325     const FileVerIt fend = filever.end();
1326     for (FileVerIt it = filever.begin(); it != fend; ++it) {
1327         QStringPair &pair(it.value());
1328         if (pair.first.contains(QLatin1String("CHECKEDOUT")))
1329             pair.first = ccGetPredecessor(pair.first.left(pair.first.indexOf(QLatin1String("@@"))));
1330         result += diffExternal(pair.first, pair.second, true);
1331     }
1332     m_diffPrefix.clear();
1333     const QString title = QString::fromLatin1("%1.patch").arg(activity);
1334     IEditor *editor = showOutputInEditor(title, result, diffEditorParameters.id, activity, nullptr);
1335     setWorkingDirectory(editor, topLevel);
1336 }
1337 
diffCurrentFile()1338 void ClearCasePluginPrivate::diffCurrentFile()
1339 {
1340     const VcsBasePluginState state = currentState();
1341     QTC_ASSERT(state.hasFile(), return);
1342     ccDiffWithPred(state.topLevel(), QStringList(state.relativeCurrentFile()));
1343 }
1344 
startCheckInCurrentFile()1345 void ClearCasePluginPrivate::startCheckInCurrentFile()
1346 {
1347     const VcsBasePluginState state = currentState();
1348     QTC_ASSERT(state.hasFile(), return);
1349     QString nativeFile = QDir::toNativeSeparators(state.relativeCurrentFile());
1350     startCheckIn(state.currentFileTopLevel(), QStringList(nativeFile));
1351 }
1352 
startCheckInAll()1353 void ClearCasePluginPrivate::startCheckInAll()
1354 {
1355     const VcsBasePluginState state = currentState();
1356     QTC_ASSERT(state.hasTopLevel(), return);
1357     QString topLevel = state.topLevel();
1358     QStringList files;
1359     for (StatusMap::ConstIterator iterator = m_statusMap->constBegin();
1360          iterator != m_statusMap->constEnd();
1361          ++iterator)
1362     {
1363         if (iterator.value().status == FileStatus::CheckedOut)
1364             files.append(QDir::toNativeSeparators(iterator.key()));
1365     }
1366     files.sort();
1367     startCheckIn(topLevel, files);
1368 }
1369 
startCheckInActivity()1370 void ClearCasePluginPrivate::startCheckInActivity()
1371 {
1372     QTC_ASSERT(isUcm(), return);
1373 
1374     const VcsBasePluginState state = currentState();
1375     QTC_ASSERT(state.hasProject(), return);
1376 
1377     QDialog dlg;
1378     auto layout = new QVBoxLayout(&dlg);
1379     auto actSelector = new ActivitySelector(&dlg);
1380     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg);
1381     connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
1382     connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
1383     layout->addWidget(actSelector);
1384     layout->addWidget(buttonBox);
1385     dlg.setWindowTitle(tr("Check In Activity"));
1386     if (!dlg.exec())
1387         return;
1388 
1389     QString topLevel = state.topLevel();
1390     int topLevelLen = topLevel.length();
1391     QStringList versions = ccGetActivityVersions(topLevel, actSelector->activity());
1392     QStringList files;
1393     QString last;
1394     foreach (const QString &version, versions) {
1395         int atatpos = version.indexOf(QLatin1String("@@"));
1396         if ((atatpos != -1) && (version.indexOf(QLatin1String("CHECKEDOUT"), atatpos) != -1)) {
1397             QString file = version.left(atatpos);
1398             if (file != last)
1399                 files.append(file.mid(topLevelLen+1));
1400             last = file;
1401         }
1402     }
1403     files.sort();
1404     startCheckIn(topLevel, files);
1405 }
1406 
1407 /* Start check in of files of a single repository by displaying
1408  * template and files in a submit editor. On closing, the real
1409  * check in will start. */
startCheckIn(const QString & workingDir,const QStringList & files)1410 void ClearCasePluginPrivate::startCheckIn(const QString &workingDir, const QStringList &files)
1411 {
1412     if (!promptBeforeCommit())
1413         return;
1414 
1415     if (raiseSubmitEditor())
1416         return;
1417 
1418     if (isCheckInEditorOpen()) {
1419         VcsOutputWindow::appendWarning(tr("Another check in is currently being executed."));
1420         return;
1421     }
1422 
1423     // Get list of added/modified/deleted files
1424     if (files.empty()) {
1425         VcsOutputWindow::appendWarning(tr("There are no modified files."));
1426         return;
1427     }
1428     // Create a new submit change file containing the submit template
1429     TempFileSaver saver;
1430     saver.setAutoRemove(false);
1431     QString submitTemplate;
1432     if (files.count() == 1)
1433         submitTemplate = ccGetComment(workingDir, files.first());
1434     // Create a submit
1435     saver.write(submitTemplate.toUtf8());
1436     if (!saver.finalize()) {
1437         VcsOutputWindow::appendError(saver.errorString());
1438         return;
1439     }
1440     m_checkInMessageFileName = saver.filePath().toString();
1441     m_checkInView = workingDir;
1442     // Create a submit editor and set file list
1443     ClearCaseSubmitEditor *editor = openClearCaseSubmitEditor(m_checkInMessageFileName, m_viewData.isUcm);
1444     setSubmitEditor(editor);
1445     editor->setStatusList(files);
1446 
1447     if (m_viewData.isUcm && (files.size() == 1)) {
1448         QString activity = ccGetFileActivity(workingDir, files.first());
1449         editor->submitEditorWidget()->setActivity(activity);
1450     }
1451 }
1452 
historyCurrentFile()1453 void ClearCasePluginPrivate::historyCurrentFile()
1454 {
1455     const VcsBasePluginState state = currentState();
1456     QTC_ASSERT(state.hasFile(), return);
1457     history(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
1458 }
1459 
updateView()1460 void ClearCasePluginPrivate::updateView()
1461 {
1462     const VcsBasePluginState state = currentState();
1463     QTC_ASSERT(state.hasTopLevel(), return);
1464     ccUpdate(state.topLevel());
1465 }
1466 
history(const QString & workingDir,const QStringList & files,bool enableAnnotationContextMenu)1467 void ClearCasePluginPrivate::history(const QString &workingDir,
1468                                const QStringList &files,
1469                                bool enableAnnotationContextMenu)
1470 {
1471     QTextCodec *codec = VcsBaseEditor::getCodec(workingDir, files);
1472     // no need for temp file
1473     QStringList args(QLatin1String("lshistory"));
1474     if (m_settings.historyCount > 0)
1475         args << QLatin1String("-last") << QString::number(m_settings.historyCount);
1476     if (!m_intStream.isEmpty())
1477         args << QLatin1String("-branch") << m_intStream;
1478     foreach (const QString &file, files)
1479         args.append(QDir::toNativeSeparators(file));
1480 
1481     const ClearCaseResponse response =
1482             runCleartool(workingDir, args, m_settings.timeOutS, 0, codec);
1483     if (response.error)
1484         return;
1485 
1486     // Re-use an existing view if possible to support
1487     // the common usage pattern of continuously changing and diffing a file
1488 
1489     const QString id = VcsBaseEditor::getTitleId(workingDir, files);
1490     const QString tag = VcsBaseEditor::editorTag(LogOutput, workingDir, files);
1491     if (IEditor *editor = VcsBaseEditor::locateEditorByTag(tag)) {
1492         editor->document()->setContents(response.stdOut.toUtf8());
1493         EditorManager::activateEditor(editor);
1494     } else {
1495         const QString title = QString::fromLatin1("cc history %1").arg(id);
1496         const QString source = VcsBaseEditor::getSource(workingDir, files);
1497         IEditor *newEditor = showOutputInEditor(title, response.stdOut, logEditorParameters.id, source, codec);
1498         VcsBaseEditor::tagEditor(newEditor, tag);
1499         if (enableAnnotationContextMenu)
1500             VcsBaseEditor::getVcsBaseEditor(newEditor)->setFileLogAnnotateEnabled(true);
1501     }
1502 }
1503 
viewStatus()1504 void ClearCasePluginPrivate::viewStatus()
1505 {
1506     if (m_viewData.name.isEmpty())
1507         m_viewData = ccGetView(m_topLevel);
1508     QTC_ASSERT(!m_viewData.name.isEmpty() && !m_settings.disableIndexer, return);
1509     VcsOutputWindow::append(QLatin1String("Indexed files status (C=Checked Out, "
1510                                           "H=Hijacked, ?=Missing)"),
1511                             VcsOutputWindow::Command, true);
1512     bool anymod = false;
1513     for (StatusMap::ConstIterator it = m_statusMap->constBegin();
1514          it != m_statusMap->constEnd();
1515          ++it)
1516     {
1517         char cstat = 0;
1518         switch (it.value().status) {
1519             case FileStatus::CheckedOut: cstat = 'C'; break;
1520             case FileStatus::Hijacked:   cstat = 'H'; break;
1521             case FileStatus::Missing:    cstat = '?'; break;
1522             default: break;
1523         }
1524         if (cstat) {
1525             VcsOutputWindow::append(QString::fromLatin1("%1    %2\n")
1526                            .arg(cstat)
1527                            .arg(QDir::toNativeSeparators(it.key())));
1528             anymod = true;
1529         }
1530     }
1531     if (!anymod)
1532         VcsOutputWindow::appendWarning(QLatin1String("No modified files found."));
1533 }
1534 
ccUpdate(const QString & workingDir,const QStringList & relativePaths)1535 void ClearCasePluginPrivate::ccUpdate(const QString &workingDir, const QStringList &relativePaths)
1536 {
1537     QStringList args(QLatin1String("update"));
1538     args << QLatin1String("-noverwrite");
1539     if (!relativePaths.isEmpty())
1540         args.append(relativePaths);
1541     const ClearCaseResponse response =
1542             runCleartool(workingDir, args, m_settings.longTimeOutS(), VcsCommand::ShowStdOut);
1543     if (!response.error)
1544         emit repositoryChanged(workingDir);
1545 }
1546 
annotateCurrentFile()1547 void ClearCasePluginPrivate::annotateCurrentFile()
1548 {
1549     const VcsBasePluginState state = currentState();
1550     QTC_ASSERT(state.hasFile(), return);
1551     vcsAnnotateHelper(state.currentFileTopLevel(), state.relativeCurrentFile());
1552 }
1553 
vcsAnnotateHelper(const QString & workingDir,const QString & file,const QString & revision,int lineNumber) const1554 void ClearCasePluginPrivate::vcsAnnotateHelper(const QString &workingDir, const QString &file,
1555                                                const QString &revision /* = QString() */,
1556                                                int lineNumber /* = -1 */) const
1557 {
1558     if (Constants::debug)
1559         qDebug() << Q_FUNC_INFO << file;
1560 
1561     QTextCodec *codec = VcsBaseEditor::getCodec(file);
1562 
1563     // Determine id
1564     QString id = file;
1565     if (!revision.isEmpty())
1566         id += QLatin1String("@@") + revision;
1567 
1568     QStringList args(QLatin1String("annotate"));
1569     args << QLatin1String("-nco") << QLatin1String("-f");
1570     args << QLatin1String("-fmt") << QLatin1String("%-14.14Sd %-8.8u | ");
1571     args << QLatin1String("-out") << QLatin1String("-");
1572     args.append(QDir::toNativeSeparators(id));
1573 
1574     const ClearCaseResponse response =
1575             runCleartool(workingDir, args, m_settings.timeOutS, 0, codec);
1576     if (response.error)
1577         return;
1578 
1579     // Re-use an existing view if possible to support
1580     // the common usage pattern of continuously changing and diffing a file
1581     const QString source = workingDir + QLatin1Char('/') + file;
1582     if (lineNumber <= 0)
1583         lineNumber = VcsBaseEditor::lineNumberOfCurrentEditor(source);
1584 
1585     QString headerSep(QLatin1String("-------------------------------------------------"));
1586     int pos = qMax(0, response.stdOut.indexOf(headerSep));
1587     // there are 2 identical headerSep lines - skip them
1588     int dataStart = response.stdOut.indexOf(QLatin1Char('\n'), pos) + 1;
1589     dataStart = response.stdOut.indexOf(QLatin1Char('\n'), dataStart) + 1;
1590     QString res;
1591     QTextStream stream(&res, QIODevice::WriteOnly | QIODevice::Text);
1592     stream << response.stdOut.mid(dataStart) << headerSep << QLatin1Char('\n')
1593            << headerSep << QLatin1Char('\n') << response.stdOut.left(pos);
1594     const QStringList files = QStringList(file);
1595     const QString tag = VcsBaseEditor::editorTag(AnnotateOutput, workingDir, files);
1596     if (IEditor *editor = VcsBaseEditor::locateEditorByTag(tag)) {
1597         editor->document()->setContents(res.toUtf8());
1598         VcsBaseEditor::gotoLineOfEditor(editor, lineNumber);
1599         EditorManager::activateEditor(editor);
1600     } else {
1601         const QString title = QString::fromLatin1("cc annotate %1").arg(id);
1602         IEditor *newEditor = showOutputInEditor(title, res, annotateEditorParameters.id, source, codec);
1603         VcsBaseEditor::tagEditor(newEditor, tag);
1604         VcsBaseEditor::gotoLineOfEditor(newEditor, lineNumber);
1605     }
1606 }
1607 
vcsDescribe(const QString & source,const QString & changeNr)1608 void ClearCasePluginPrivate::vcsDescribe(const QString &source, const QString &changeNr)
1609 {
1610     const QFileInfo fi(source);
1611     QString topLevel;
1612     const bool manages = managesDirectory(fi.isDir() ? source : fi.absolutePath(), &topLevel);
1613     if (!manages || topLevel.isEmpty())
1614         return;
1615     if (Constants::debug)
1616         qDebug() << Q_FUNC_INFO << source << topLevel << changeNr;
1617     QString description;
1618     QString relPath = QDir::toNativeSeparators(QDir(topLevel).relativeFilePath(source));
1619     QString id = QString::fromLatin1("%1@@%2").arg(relPath).arg(changeNr);
1620 
1621     QStringList args(QLatin1String("describe"));
1622     args.push_back(id);
1623     QTextCodec *codec = VcsBaseEditor::getCodec(source);
1624     const ClearCaseResponse response = runCleartool(topLevel, args, m_settings.timeOutS, 0, codec);
1625     description = response.stdOut;
1626     if (m_settings.extDiffAvailable)
1627         description += diffExternal(id);
1628 
1629     // Re-use an existing view if possible to support
1630     // the common usage pattern of continuously changing and diffing a file
1631     const QString tag = VcsBaseEditor::editorTag(DiffOutput, source, QStringList(), changeNr);
1632     if (IEditor *editor = VcsBaseEditor::locateEditorByTag(tag)) {
1633         editor->document()->setContents(description.toUtf8());
1634         EditorManager::activateEditor(editor);
1635     } else {
1636         const QString title = QString::fromLatin1("cc describe %1").arg(id);
1637         IEditor *newEditor = showOutputInEditor(title, description, diffEditorParameters.id, source, codec);
1638         VcsBaseEditor::tagEditor(newEditor, tag);
1639     }
1640 }
1641 
commitFromEditor()1642 void ClearCasePluginPrivate::commitFromEditor()
1643 {
1644     m_submitActionTriggered = true;
1645     QTC_ASSERT(submitEditor(), return);
1646     EditorManager::closeDocuments({submitEditor()->document()});
1647 }
1648 
runCleartoolSync(const QString & workingDir,const QStringList & arguments) const1649 QString ClearCasePluginPrivate::runCleartoolSync(const QString &workingDir,
1650                                           const QStringList &arguments) const
1651 {
1652     return runCleartool(workingDir, arguments, m_settings.timeOutS, SilentRun).stdOut;
1653 }
1654 
1655 ClearCaseResponse
runCleartool(const QString & workingDir,const QStringList & arguments,int timeOutS,unsigned flags,QTextCodec * outputCodec) const1656 ClearCasePluginPrivate::runCleartool(const QString &workingDir,
1657                               const QStringList &arguments,
1658                               int timeOutS,
1659                               unsigned flags,
1660                               QTextCodec *outputCodec) const
1661 {
1662     const QString executable = m_settings.ccBinaryPath;
1663     ClearCaseResponse response;
1664     if (executable.isEmpty()) {
1665         response.error = true;
1666         response.message = tr("No ClearCase executable specified.");
1667         return response;
1668     }
1669 
1670     QtcProcess proc;
1671     proc.setTimeoutS(timeOutS);
1672 
1673     VcsCommand command(workingDir, Environment::systemEnvironment());
1674     command.addFlags(flags);
1675     command.setCodec(outputCodec);
1676     command.runCommand(proc, {executable, arguments});
1677 
1678     response.error = proc.result() != QtcProcess::FinishedWithSuccess;
1679     if (response.error)
1680         response.message = proc.exitMessage();
1681     response.stdErr = proc.stdErr();
1682     response.stdOut = proc.stdOut();
1683     return response;
1684 }
1685 
showOutputInEditor(const QString & title,const QString & output,Utils::Id id,const QString & source,QTextCodec * codec) const1686 IEditor *ClearCasePluginPrivate::showOutputInEditor(const QString& title, const QString &output,
1687                                                    Utils::Id id, const QString &source,
1688                                                    QTextCodec *codec) const
1689 {
1690     if (Constants::debug)
1691         qDebug() << "ClearCasePlugin::showOutputInEditor" << title << id.name()
1692                  <<  "Size= " << output.size() << debugCodec(codec);
1693     QString s = title;
1694     IEditor *editor = EditorManager::openEditorWithContents(id, &s, output.toUtf8());
1695     auto e = qobject_cast<ClearCaseEditorWidget*>(editor->widget());
1696     if (!e)
1697         return nullptr;
1698     connect(e, &VcsBaseEditorWidget::annotateRevisionRequested,
1699             this, &ClearCasePluginPrivate::vcsAnnotateHelper);
1700     e->setForceReadOnly(true);
1701     s.replace(QLatin1Char(' '), QLatin1Char('_'));
1702     e->textDocument()->setFallbackSaveAsFileName(s);
1703     if (!source.isEmpty())
1704         e->setSource(source);
1705     if (codec)
1706         e->setCodec(codec);
1707     return editor;
1708 }
1709 
settings() const1710 const ClearCaseSettings &ClearCasePluginPrivate::settings() const
1711 {
1712     return m_settings;
1713 }
1714 
setSettings(const ClearCaseSettings & s)1715 void ClearCasePluginPrivate::setSettings(const ClearCaseSettings &s)
1716 {
1717     if (s != m_settings) {
1718         m_settings = s;
1719         m_settings.toSettings(ICore::settings());
1720         emit configurationChanged();
1721     }
1722 }
1723 
instance()1724 ClearCasePluginPrivate *ClearCasePluginPrivate::instance()
1725 {
1726     QTC_ASSERT(dd, return dd);
1727     return dd;
1728 }
1729 
vcsOpen(const QString & workingDir,const QString & fileName)1730 bool ClearCasePluginPrivate::vcsOpen(const QString &workingDir, const QString &fileName)
1731 {
1732     QTC_ASSERT(currentState().hasTopLevel(), return false);
1733 
1734     if (Constants::debug)
1735         qDebug() << Q_FUNC_INFO << workingDir << fileName;
1736 
1737     QFileInfo fi(workingDir, fileName);
1738     QString topLevel = currentState().topLevel();
1739     QString absPath = fi.absoluteFilePath();
1740 
1741     if (!m_settings.disableIndexer &&
1742             (fi.isWritable() || vcsStatus(absPath).status == FileStatus::Unknown))
1743         Utils::runAsync(sync, QStringList(absPath)).waitForFinished();
1744     if (vcsStatus(absPath).status == FileStatus::CheckedOut) {
1745         QMessageBox::information(ICore::dialogParent(), tr("ClearCase Checkout"),
1746                                  tr("File is already checked out."));
1747         return true;
1748     }
1749 
1750     const QString relFile = QDir(topLevel).relativeFilePath(absPath);
1751     const QString file = QDir::toNativeSeparators(relFile);
1752     const QString title = QString::fromLatin1("Checkout %1").arg(file);
1753     CheckOutDialog coDialog(title, m_viewData.isUcm, !m_settings.noComment);
1754 
1755     // Only snapshot views can have hijacked files
1756     bool isHijacked = (!m_viewData.isDynamic && (vcsStatus(absPath).status & FileStatus::Hijacked));
1757     if (!isHijacked)
1758         coDialog.hideHijack();
1759     if (coDialog.exec() == QDialog::Accepted) {
1760         if (m_viewData.isUcm && !vcsSetActivity(topLevel, title, coDialog.activity()))
1761             return false;
1762 
1763         FileChangeBlocker fcb(FilePath::fromString(absPath));
1764         QStringList args(QLatin1String("checkout"));
1765 
1766         const QString comment = coDialog.comment();
1767         if (m_settings.noComment || comment.isEmpty())
1768             args << QLatin1String("-nc");
1769         else
1770             args << QLatin1String("-c") << comment;
1771 
1772         args << QLatin1String("-query");
1773         const bool reserved = coDialog.isReserved();
1774         const bool unreserved = !reserved || coDialog.isUnreserved();
1775         if (reserved)
1776             args << QLatin1String("-reserved");
1777         if (unreserved)
1778             args << QLatin1String("-unreserved");
1779         if (coDialog.isPreserveTime())
1780             args << QLatin1String("-ptime");
1781         if (isHijacked) {
1782             if (Constants::debug)
1783                 qDebug() << Q_FUNC_INFO << file << " seems to be hijacked";
1784 
1785             // A hijacked files means that the file is modified but was
1786             // not checked out. By checking it out now changes will
1787             // be lost, unless handled. This can be done by renaming
1788             // the hijacked file, undoing the hijack and updating the file
1789 
1790             // -usehijack not supported in old cleartool versions...
1791             // args << QLatin1String("-usehijack");
1792             if (coDialog.isUseHijacked())
1793                 QFile::rename(absPath, absPath + QLatin1String(".hijack"));
1794             vcsUndoHijack(topLevel, relFile, false); // don't keep, we've already kept a copy
1795         }
1796         args << file;
1797         ClearCaseResponse response =
1798                 runCleartool(topLevel, args, m_settings.timeOutS,
1799                              VcsCommand::ShowStdOut
1800                              | VcsCommand::SuppressStdErr
1801                              | VcsCommand::FullySynchronously);
1802         if (response.error) {
1803             if (response.stdErr.contains(QLatin1String("Versions other than the selected version"))) {
1804                 VersionSelector selector(file, response.stdErr);
1805                 if (selector.exec() == QDialog::Accepted) {
1806                     if (selector.isUpdate())
1807                         ccUpdate(workingDir, QStringList(file));
1808                     else
1809                         args.removeOne(QLatin1String("-query"));
1810                     response = runCleartool(topLevel, args, m_settings.timeOutS,
1811                                             VcsCommand::ShowStdOut
1812                                             | VcsCommand::FullySynchronously);
1813                 }
1814             } else {
1815                 VcsOutputWindow::append(response.stdOut);
1816                 VcsOutputWindow::appendError(response.stdErr);
1817             }
1818         }
1819 
1820         if (!response.error && isHijacked && coDialog.isUseHijacked()) { // rename back
1821             QFile::remove(absPath);
1822             QFile::rename(absPath + QLatin1String(".hijack"), absPath);
1823         }
1824 
1825         if ((!response.error || response.stdErr.contains(QLatin1String("already checked out")))
1826                 && !m_settings.disableIndexer) {
1827             setStatus(absPath, FileStatus::CheckedOut);
1828         }
1829 
1830         foreach (DocumentModel::Entry *e, DocumentModel::entries()) {
1831             if (e->fileName().toString() == absPath) {
1832                 e->document->checkPermissions();
1833                 break;
1834             }
1835         }
1836 
1837         return !response.error;
1838     }
1839     return true;
1840 }
1841 
vcsSetActivity(const QString & workingDir,const QString & title,const QString & activity)1842 bool ClearCasePluginPrivate::vcsSetActivity(const QString &workingDir, const QString &title, const QString &activity)
1843 {
1844     QStringList args;
1845     args << QLatin1String("setactivity") << activity;
1846     const ClearCaseResponse actResponse =
1847             runCleartool(workingDir, args, m_settings.timeOutS, VcsCommand::ShowStdOut);
1848     if (actResponse.error) {
1849         QMessageBox::warning(ICore::dialogParent(), title,
1850                              tr("Set current activity failed: %1").arg(actResponse.message), QMessageBox::Ok);
1851         return false;
1852     }
1853     m_activity = activity;
1854     return true;
1855 }
1856 
1857 // files are received using native separators
vcsCheckIn(const QString & messageFile,const QStringList & files,const QString & activity,bool isIdentical,bool isPreserve,bool replaceActivity)1858 bool ClearCasePluginPrivate::vcsCheckIn(const QString &messageFile, const QStringList &files, const QString &activity,
1859                                  bool isIdentical, bool isPreserve, bool replaceActivity)
1860 {
1861     if (Constants::debug)
1862         qDebug() << Q_FUNC_INFO << messageFile << files << activity;
1863     if (files.isEmpty())
1864         return true;
1865     const QString title = QString::fromLatin1("Checkin %1").arg(files.join(QLatin1String("; ")));
1866     using FCBPointer = QSharedPointer<FileChangeBlocker>;
1867     replaceActivity &= (activity != QLatin1String(Constants::KEEP_ACTIVITY));
1868     if (replaceActivity && !vcsSetActivity(m_checkInView, title, activity))
1869         return false;
1870     QString message;
1871     QFile msgFile(messageFile);
1872     if (msgFile.open(QFile::ReadOnly | QFile::Text)) {
1873         message = QString::fromLocal8Bit(msgFile.readAll().trimmed());
1874         msgFile.close();
1875     }
1876     QStringList args;
1877     args << QLatin1String("checkin");
1878     if (message.isEmpty())
1879         args << QLatin1String("-nc");
1880     else
1881         args << QLatin1String("-cfile") << messageFile;
1882     if (isIdentical)
1883         args << QLatin1String("-identical");
1884     if (isPreserve)
1885         args << QLatin1String("-ptime");
1886     args << files;
1887     QList<FCBPointer> blockers;
1888     foreach (const QString &fileName, files) {
1889         FCBPointer fcb(new FileChangeBlocker(
1890             FilePath::fromString(QFileInfo(m_checkInView, fileName).canonicalFilePath())));
1891         blockers.append(fcb);
1892     }
1893     const ClearCaseResponse response =
1894             runCleartool(m_checkInView, args, m_settings.longTimeOutS(),
1895                          VcsCommand::ShowStdOut);
1896     const QRegularExpression checkedIn("Checked in \\\"([^\"]*)\\\"");
1897     QRegularExpressionMatch match = checkedIn.match(response.stdOut);
1898     bool anySucceeded = false;
1899     int offset = match.capturedStart();
1900     while (match.hasMatch()) {
1901         QString file = match.captured(1);
1902         QFileInfo fi(m_checkInView, file);
1903         QString absPath = fi.absoluteFilePath();
1904 
1905         if (!m_settings.disableIndexer)
1906             setStatus(QDir::fromNativeSeparators(absPath), FileStatus::CheckedIn);
1907         emit filesChanged(files);
1908         anySucceeded = true;
1909         match = checkedIn.match(response.stdOut, offset + 12);
1910         offset = match.capturedStart();
1911     }
1912     return anySucceeded;
1913 }
1914 
ccFileOp(const QString & workingDir,const QString & title,const QStringList & opArgs,const QString & fileName,const QString & file2)1915 bool ClearCasePluginPrivate::ccFileOp(const QString &workingDir, const QString &title, const QStringList &opArgs,
1916                                const QString &fileName, const QString &file2)
1917 {
1918     const QString file = QDir::toNativeSeparators(fileName);
1919     bool noCheckout = false;
1920     ActivitySelector *actSelector = nullptr;
1921     QDialog fileOpDlg;
1922     fileOpDlg.setWindowTitle(title);
1923 
1924     auto verticalLayout = new QVBoxLayout(&fileOpDlg);
1925     if (m_viewData.isUcm) {
1926         actSelector = new ActivitySelector;
1927         verticalLayout->addWidget(actSelector);
1928     }
1929 
1930     auto commentLabel = new QLabel(tr("Enter &comment:"));
1931     verticalLayout->addWidget(commentLabel);
1932 
1933     auto commentEdit = new QTextEdit;
1934     verticalLayout->addWidget(commentEdit);
1935 
1936     auto buttonBox = new QDialogButtonBox;
1937     buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
1938     verticalLayout->addWidget(buttonBox);
1939 
1940     commentLabel->setBuddy(commentEdit);
1941 
1942     connect(buttonBox, &QDialogButtonBox::accepted, &fileOpDlg, &QDialog::accept);
1943     connect(buttonBox, &QDialogButtonBox::rejected, &fileOpDlg, &QDialog::reject);
1944 
1945     if (!fileOpDlg.exec())
1946         return false;
1947 
1948     QString comment = commentEdit->toPlainText();
1949     if (m_viewData.isUcm && actSelector->changed())
1950         vcsSetActivity(workingDir, fileOpDlg.windowTitle(), actSelector->activity());
1951 
1952     QString dirName = QDir::toNativeSeparators(QFileInfo(workingDir, fileName).absolutePath());
1953     QStringList commentArg;
1954     if (comment.isEmpty())
1955         commentArg << QLatin1String("-nc");
1956     else
1957         commentArg << QLatin1String("-c") << comment;
1958 
1959     // check out directory
1960     QStringList args;
1961     args << QLatin1String("checkout") << commentArg << dirName;
1962     const ClearCaseResponse coResponse =
1963         runCleartool(workingDir, args, m_settings.timeOutS,
1964                      VcsCommand::ShowStdOut | VcsCommand::FullySynchronously);
1965     if (coResponse.error) {
1966         if (coResponse.stdErr.contains(QLatin1String("already checked out")))
1967             noCheckout = true;
1968         else
1969             return false;
1970     }
1971 
1972     // do the file operation
1973     args.clear();
1974     args << opArgs << commentArg << file;
1975     if (!file2.isEmpty())
1976         args << QDir::toNativeSeparators(file2);
1977     const ClearCaseResponse opResponse =
1978             runCleartool(workingDir, args, m_settings.timeOutS,
1979                          VcsCommand::ShowStdOut | VcsCommand::FullySynchronously);
1980     if (opResponse.error) {
1981         // on failure - undo checkout for the directory
1982         if (!noCheckout)
1983             vcsUndoCheckOut(workingDir, dirName, false);
1984         return false;
1985     }
1986 
1987     if (!noCheckout) {
1988         // check in the directory
1989         args.clear();
1990         args << QLatin1String("checkin") << commentArg << dirName;
1991         const ClearCaseResponse ciResponse =
1992             runCleartool(workingDir, args, m_settings.timeOutS,
1993                          VcsCommand::ShowStdOut | VcsCommand::FullySynchronously);
1994         return !ciResponse.error;
1995     }
1996     return true;
1997 }
1998 
baseName(const QString & fileName)1999 static QString baseName(const QString &fileName)
2000 {
2001     return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1);
2002 }
2003 
vcsAdd(const QString & workingDir,const QString & fileName)2004 bool ClearCasePluginPrivate::vcsAdd(const QString &workingDir, const QString &fileName)
2005 {
2006     return ccFileOp(workingDir, tr("ClearCase Add File %1").arg(baseName(fileName)),
2007                     QStringList({"mkelem", "-ci"}), fileName);
2008 }
2009 
vcsDelete(const QString & workingDir,const QString & fileName)2010 bool ClearCasePluginPrivate::vcsDelete(const QString &workingDir, const QString &fileName)
2011 {
2012     const QString title(tr("ClearCase Remove Element %1").arg(baseName(fileName)));
2013     if (QMessageBox::warning(ICore::dialogParent(), title, tr("This operation is irreversible. Are you sure?"),
2014                          QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
2015         return true;
2016 
2017     return ccFileOp(workingDir, tr("ClearCase Remove File %1").arg(baseName(fileName)),
2018                     QStringList({"rmname", "-force"}), fileName);
2019 }
2020 
vcsMove(const QString & workingDir,const QString & from,const QString & to)2021 bool ClearCasePluginPrivate::vcsMove(const QString &workingDir, const QString &from, const QString &to)
2022 {
2023     return ccFileOp(workingDir, tr("ClearCase Rename File %1 -> %2")
2024                     .arg(baseName(from)).arg(baseName(to)),
2025                     QStringList("move"), from, to);
2026 }
2027 
2028 ///
2029 /// Check if the directory is managed under ClearCase control.
2030 ///
managesDirectory(const QString & directory,QString * topLevel) const2031 bool ClearCasePluginPrivate::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
2032 {
2033 #ifdef WITH_TESTS
2034     // If running with tests and fake ClearTool is enabled, then pretend we manage every directory
2035     QString topLevelFound = m_fakeClearTool ? directory : findTopLevel(directory);
2036 #else
2037     QString topLevelFound = findTopLevel(directory);
2038 #endif
2039 
2040     if (topLevel)
2041         *topLevel = topLevelFound;
2042     return !topLevelFound.isEmpty();
2043 }
2044 
ccGetCurrentActivity() const2045 QString ClearCasePluginPrivate::ccGetCurrentActivity() const
2046 {
2047     QStringList args(QLatin1String("lsactivity"));
2048     args << QLatin1String("-cact");
2049     args << QLatin1String("-fmt") << QLatin1String("%n");
2050     return runCleartoolSync(currentState().topLevel(), args);
2051 }
2052 
ccGetActivities() const2053 QList<QStringPair> ClearCasePluginPrivate::ccGetActivities() const
2054 {
2055     QList<QStringPair> result;
2056     // Maintain latest deliver and rebase activities only
2057     QStringPair rebaseAct;
2058     QStringPair deliverAct;
2059     // Retrieve all activities
2060     QStringList args(QLatin1String("lsactivity"));
2061     args << QLatin1String("-fmt") << QLatin1String("%n\\t%[headline]p\\n");
2062     const QString response = runCleartoolSync(currentState().topLevel(), args);
2063     QStringList acts = response.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
2064     foreach (const QString &activity, acts) {
2065         QStringList act = activity.split(QLatin1Char('\t'));
2066         if (act.size() >= 2)
2067         {
2068             QString actName = act.at(0);
2069             // include only latest deliver/rebase activities. Activities are sorted
2070             // by creation time
2071             if (actName.startsWith(QLatin1String("rebase.")))
2072                 rebaseAct = QStringPair(actName, act.at(1));
2073             else if (actName.startsWith(QLatin1String("deliver.")))
2074                 deliverAct = QStringPair(actName, act.at(1));
2075             else
2076                 result.append(QStringPair(actName, act.at(1).trimmed()));
2077         }
2078     }
2079     Utils::sort(result);
2080     if (!rebaseAct.first.isEmpty())
2081         result.append(rebaseAct);
2082     if (!deliverAct.first.isEmpty())
2083         result.append(deliverAct);
2084     return result;
2085 }
2086 
refreshActivities()2087 void ClearCasePluginPrivate::refreshActivities()
2088 {
2089     QMutexLocker locker(&m_activityMutex);
2090     m_activity = ccGetCurrentActivity();
2091     m_activities = ccGetActivities();
2092 }
2093 
activities(int * current)2094 QList<QStringPair> ClearCasePluginPrivate::activities(int *current)
2095 {
2096     QList<QStringPair> activitiesList;
2097     QString curActivity;
2098     const VcsBasePluginState state = currentState();
2099     if (state.topLevel() == state.currentProjectTopLevel()) {
2100         QMutexLocker locker(&m_activityMutex);
2101         activitiesList = m_activities;
2102         curActivity = m_activity;
2103     } else {
2104         activitiesList = ccGetActivities();
2105         curActivity = ccGetCurrentActivity();
2106     }
2107     if (current) {
2108         int nActivities = activitiesList.size();
2109         *current = -1;
2110         for (int i = 0; i < nActivities && (*current == -1); ++i) {
2111             if (activitiesList[i].first == curActivity)
2112                 *current = i;
2113         }
2114     }
2115     return activitiesList;
2116 }
2117 
newActivity()2118 bool ClearCasePluginPrivate::newActivity()
2119 {
2120     QString workingDir = currentState().topLevel();
2121     QStringList args;
2122     args << QLatin1String("mkactivity") << QLatin1String("-f");
2123     if (!m_settings.autoAssignActivityName) {
2124         QString headline = QInputDialog::getText(ICore::dialogParent(), tr("Activity Headline"),
2125                                                  tr("Enter activity headline"));
2126         if (headline.isEmpty())
2127             return false;
2128         args << QLatin1String("-headline") << headline;
2129     }
2130 
2131     const ClearCaseResponse response =
2132             runCleartool(workingDir, args, m_settings.timeOutS, 0);
2133 
2134     if (!response.error)
2135         refreshActivities();
2136     return (!response.error);
2137 }
2138 
2139 // check if the view is UCM
ccCheckUcm(const QString & viewname,const QString & workingDir) const2140 bool ClearCasePluginPrivate::ccCheckUcm(const QString &viewname, const QString &workingDir) const
2141 {
2142     QStringList catcsArgs(QLatin1String("catcs"));
2143     catcsArgs << QLatin1String("-tag") << viewname;
2144     QString catcsData = runCleartoolSync(workingDir, catcsArgs);
2145 
2146     // check output for the word "ucm"
2147     return catcsData.indexOf(QRegularExpression("(^|\\n)ucm\\n")) != -1;
2148 }
2149 
managesFile(const QString & workingDirectory,const QString & fileName) const2150 bool ClearCasePluginPrivate::managesFile(const QString &workingDirectory, const QString &fileName) const
2151 {
2152     QString absFile = QFileInfo(QDir(workingDirectory), fileName).absoluteFilePath();
2153     const FileStatus::Status status = getFileStatus(absFile);
2154     return status != FileStatus::NotManaged && status != FileStatus::Derived;
2155 }
2156 
ccGetView(const QString & workingDir) const2157 ViewData ClearCasePluginPrivate::ccGetView(const QString &workingDir) const
2158 {
2159     static QHash<QString, ViewData> viewCache;
2160 
2161     bool inCache = viewCache.contains(workingDir);
2162     ViewData &res = viewCache[workingDir];
2163     if (!inCache) {
2164         QStringList args(QLatin1String("lsview"));
2165         args << QLatin1String("-cview");
2166         QString data = runCleartoolSync(workingDir, args);
2167         res.isDynamic = !data.isEmpty() && (data.at(0) == QLatin1Char('*'));
2168         res.name = data.mid(2, data.indexOf(QLatin1Char(' '), 2) - 2);
2169         res.isUcm = ccCheckUcm(res.name, workingDir);
2170         res.root = ccViewRoot(workingDir);
2171     }
2172 
2173     return res;
2174 }
2175 
ccGetComment(const QString & workingDir,const QString & fileName) const2176 QString ClearCasePluginPrivate::ccGetComment(const QString &workingDir, const QString &fileName) const
2177 {
2178     QStringList args(QLatin1String("describe"));
2179     args << QLatin1String("-fmt") << QLatin1String("%c") << fileName;
2180     return runCleartoolSync(workingDir, args);
2181 }
2182 
updateStreamAndView()2183 void ClearCasePluginPrivate::updateStreamAndView()
2184 {
2185     QStringList args(QLatin1String("lsstream"));
2186     args << QLatin1String("-fmt") << QLatin1String("%n\\t%[def_deliver_tgt]Xp");
2187     const QString sresponse = runCleartoolSync(m_topLevel, args);
2188     int tabPos = sresponse.indexOf(QLatin1Char('\t'));
2189     m_stream = sresponse.left(tabPos);
2190     const QRegularExpression intStreamExp("stream:([^@]*)");
2191     const QRegularExpressionMatch match = intStreamExp.match(sresponse.mid(tabPos + 1));
2192     if (match.hasMatch())
2193         m_intStream = match.captured(1);
2194     m_viewData = ccGetView(m_topLevel);
2195     m_updateViewAction->setParameter(m_viewData.isDynamic ? QString() : m_viewData.name);
2196 }
2197 
projectChanged(Project * project)2198 void ClearCasePluginPrivate::projectChanged(Project *project)
2199 {
2200     if (m_viewData.name == ccGetView(m_topLevel).name) // New project on same view as old project
2201         return;
2202     m_viewData = ViewData();
2203     m_stream.clear();
2204     m_intStream.clear();
2205     ProgressManager::cancelTasks(ClearCase::Constants::TASK_INDEX);
2206     if (project) {
2207         QString projDir = project->projectDirectory().toString();
2208         QString topLevel = findTopLevel(projDir);
2209         m_topLevel = topLevel;
2210         if (topLevel.isEmpty())
2211             return;
2212         connect(qApp, &QApplication::applicationStateChanged,
2213                 this, [this](Qt::ApplicationState state) {
2214                     if (state == Qt::ApplicationActive)
2215                         syncSlot();
2216                 });
2217         updateStreamAndView();
2218         if (m_viewData.name.isEmpty())
2219             return;
2220         updateIndex();
2221     }
2222     if (Constants::debug)
2223         qDebug() << "stream: " << m_stream << "; intStream: " << m_intStream << "view: " << m_viewData.name;
2224 }
2225 
tasksFinished(Id type)2226 void ClearCasePluginPrivate::tasksFinished(Id type)
2227 {
2228     if (type == ClearCase::Constants::TASK_INDEX)
2229         m_checkInAllAction->setEnabled(true);
2230 }
2231 
updateIndex()2232 void ClearCasePluginPrivate::updateIndex()
2233 {
2234     QTC_ASSERT(currentState().hasTopLevel(), return);
2235     ProgressManager::cancelTasks(ClearCase::Constants::TASK_INDEX);
2236     Project *project = SessionManager::startupProject();
2237     if (!project)
2238         return;
2239     m_checkInAllAction->setEnabled(false);
2240     m_statusMap->clear();
2241     QFuture<void> result = Utils::runAsync(sync, transform(project->files(Project::SourceFiles), &FilePath::toString));
2242     if (!m_settings.disableIndexer)
2243         ProgressManager::addTask(result, tr("Updating ClearCase Index"), ClearCase::Constants::TASK_INDEX);
2244 }
2245 
2246 /*! retrieve a \a file (usually of the form path\to\filename.cpp@@\main\ver)
2247  *  from cc and save it to a temporary location which is returned
2248  */
getFile(const QString & nativeFile,const QString & prefix)2249 QString ClearCasePluginPrivate::getFile(const QString &nativeFile, const QString &prefix)
2250 {
2251     QString tempFile;
2252     QDir tempDir = QDir::temp();
2253     tempDir.mkdir(QLatin1String("ccdiff"));
2254     tempDir.cd(QLatin1String("ccdiff"));
2255     int atatpos = nativeFile.indexOf(QLatin1String("@@"));
2256     QString file = QDir::fromNativeSeparators(nativeFile.left(atatpos));
2257     if (prefix.isEmpty()) {
2258         tempFile = tempDir.absoluteFilePath(QString::number(QUuid::createUuid().data1, 16));
2259     } else {
2260         tempDir.mkpath(prefix);
2261         tempDir.cd(prefix);
2262         int slash = file.lastIndexOf(QLatin1Char('/'));
2263         if (slash != -1)
2264             tempDir.mkpath(file.left(slash));
2265         tempFile = tempDir.absoluteFilePath(file);
2266     }
2267     if (Constants::debug)
2268         qDebug() << Q_FUNC_INFO << nativeFile;
2269     if ((atatpos != -1) && (nativeFile.indexOf(QLatin1String("CHECKEDOUT"), atatpos) != -1)) {
2270         bool res = QFile::copy(QDir(m_topLevel).absoluteFilePath(file), tempFile);
2271         return res ? tempFile : QString();
2272     }
2273     QStringList args(QLatin1String("get"));
2274     args << QLatin1String("-to") << tempFile << nativeFile;
2275     const ClearCaseResponse response =
2276             runCleartool(m_topLevel, args, m_settings.timeOutS, SilentRun);
2277     if (response.error)
2278         return QString();
2279     QFile::setPermissions(tempFile, QFile::ReadOwner | QFile::ReadUser |
2280                           QFile::WriteOwner | QFile::WriteUser);
2281     return tempFile;
2282 }
2283 
2284 // runs external (GNU) diff, and returns the stdout result
diffExternal(QString file1,QString file2,bool keep)2285 QString ClearCasePluginPrivate::diffExternal(QString file1, QString file2, bool keep)
2286 {
2287     QTextCodec *codec = VcsBaseEditor::getCodec(file1);
2288 
2289     // if file2 is empty, we should compare to predecessor
2290     if (file2.isEmpty()) {
2291         QString predVer = ccGetPredecessor(file1);
2292         return (predVer.isEmpty() ? QString() : diffExternal(predVer, file1, keep));
2293     }
2294 
2295     file1 = QDir::toNativeSeparators(file1);
2296     file2 = QDir::toNativeSeparators(file2);
2297     QString tempFile1, tempFile2;
2298     QString prefix = m_diffPrefix;
2299     if (!prefix.isEmpty())
2300         prefix.append(QLatin1Char('/'));
2301 
2302     if (file1.contains(QLatin1String("@@")))
2303         tempFile1 = getFile(file1, prefix + QLatin1String("old"));
2304     if (file2.contains(QLatin1String("@@")))
2305         tempFile2 = getFile(file2, prefix + QLatin1String("new"));
2306     QStringList args;
2307     if (!tempFile1.isEmpty()) {
2308         args << QLatin1String("-L") << file1;
2309         args << tempFile1;
2310     } else {
2311         args << file1;
2312     }
2313     if (!tempFile2.isEmpty()) {
2314         args << QLatin1String("-L") << file2;
2315         args << tempFile2;
2316     } else {
2317         args << file2;
2318     }
2319     const QString diffResponse = runExtDiff(m_topLevel, args, m_settings.timeOutS, codec);
2320     if (!keep && !tempFile1.isEmpty()) {
2321         QFile::remove(tempFile1);
2322         QFileInfo(tempFile1).dir().rmpath(QLatin1String("."));
2323     }
2324     if (!keep && !tempFile2.isEmpty()) {
2325         QFile::remove(tempFile2);
2326         QFileInfo(tempFile2).dir().rmpath(QLatin1String("."));
2327     }
2328     if (diffResponse.isEmpty())
2329         return QLatin1String("Files are identical");
2330     QString header = QString::fromLatin1("diff %1 old/%2 new/%2\n")
2331             .arg(m_settings.diffArgs)
2332             .arg(QDir::fromNativeSeparators(file2.left(file2.indexOf(QLatin1String("@@")))));
2333     return header + diffResponse;
2334 }
2335 
2336 // runs builtin diff (either graphical or diff_format)
diffGraphical(const QString & file1,const QString & file2)2337 void ClearCasePluginPrivate::diffGraphical(const QString &file1, const QString &file2)
2338 {
2339     QStringList args;
2340     bool pred = file2.isEmpty();
2341     args.push_back(QLatin1String("diff"));
2342     if (pred)
2343         args.push_back(QLatin1String("-predecessor"));
2344     args.push_back(QLatin1String("-graphical"));
2345     args << file1;
2346     if (!pred)
2347         args << file2;
2348     QProcess::startDetached(m_settings.ccBinaryPath, args, m_topLevel);
2349 }
2350 
runExtDiff(const QString & workingDir,const QStringList & arguments,int timeOutS,QTextCodec * outputCodec)2351 QString ClearCasePluginPrivate::runExtDiff(const QString &workingDir, const QStringList &arguments,
2352                                     int timeOutS, QTextCodec *outputCodec)
2353 {
2354     CommandLine diff("diff");
2355     diff.addArgs(m_settings.diffArgs.split(' ', Qt::SkipEmptyParts));
2356     diff.addArgs(arguments);
2357 
2358     QtcProcess process;
2359     process.setTimeoutS(timeOutS);
2360     process.setWorkingDirectory(workingDir);
2361     process.setCodec(outputCodec ? outputCodec : QTextCodec::codecForName("UTF-8"));
2362     process.setCommand(diff);
2363     process.setProcessUserEventWhileRunning();
2364     process.runBlocking();
2365     if (process.result() != QtcProcess::FinishedWithSuccess)
2366         return QString();
2367     return process.allOutput();
2368 }
2369 
syncSlot()2370 void ClearCasePluginPrivate::syncSlot()
2371 {
2372     VcsBasePluginState state = currentState();
2373     if (!state.hasProject() || !state.hasTopLevel())
2374         return;
2375     QString topLevel = state.topLevel();
2376     if (topLevel != state.currentProjectTopLevel())
2377         return;
2378     Utils::runAsync(sync, QStringList());
2379 }
2380 
closing()2381 void ClearCasePluginPrivate::closing()
2382 {
2383     // prevent syncSlot from being called on shutdown
2384     ProgressManager::cancelTasks(ClearCase::Constants::TASK_INDEX);
2385     disconnect(qApp, &QApplication::applicationStateChanged, nullptr, nullptr);
2386 }
2387 
sync(QFutureInterface<void> & future,QStringList files)2388 void ClearCasePluginPrivate::sync(QFutureInterface<void> &future, QStringList files)
2389 {
2390     ClearCasePluginPrivate *plugin = ClearCasePluginPrivate::instance();
2391     ClearCaseSync ccSync(plugin->m_statusMap);
2392     connect(&ccSync, &ClearCaseSync::updateStreamAndView, plugin, &ClearCasePluginPrivate::updateStreamAndView);
2393     ccSync.run(future, files);
2394 }
2395 
displayName() const2396 QString ClearCasePluginPrivate::displayName() const
2397 {
2398     return QLatin1String("ClearCase");
2399 }
2400 
id() const2401 Utils::Id ClearCasePluginPrivate::id() const
2402 {
2403     return Constants::VCS_ID_CLEARCASE;
2404 }
2405 
isVcsFileOrDirectory(const Utils::FilePath & fileName) const2406 bool ClearCasePluginPrivate::isVcsFileOrDirectory(const Utils::FilePath &fileName) const
2407 {
2408     Q_UNUSED(fileName)
2409     return false; // ClearCase has no files/directories littering the sources
2410 }
2411 
isConfigured() const2412 bool ClearCasePluginPrivate::isConfigured() const
2413 {
2414 #ifdef WITH_TESTS
2415     if (m_fakeClearTool)
2416         return true;
2417 #endif
2418     const QString binary = m_settings.ccBinaryPath;
2419     if (binary.isEmpty())
2420         return false;
2421     QFileInfo fi(binary);
2422     return fi.exists() && fi.isFile() && fi.isExecutable();
2423 }
2424 
supportsOperation(Operation operation) const2425 bool ClearCasePluginPrivate::supportsOperation(Operation operation) const
2426 {
2427     bool rc = isConfigured();
2428     switch (operation) {
2429     case AddOperation:
2430     case DeleteOperation:
2431     case MoveOperation:
2432     case AnnotateOperation:
2433         break;
2434     case CreateRepositoryOperation:
2435     case SnapshotOperations:
2436     case Core::IVersionControl::InitialCheckoutOperation:
2437         rc = false;
2438         break;
2439     }
2440     return rc;
2441 }
2442 
openSupportMode(const QString & fileName) const2443 Core::IVersionControl::OpenSupportMode ClearCasePluginPrivate::openSupportMode(const QString &fileName) const
2444 {
2445     if (isDynamic()) {
2446         // NB! Has to use managesFile() and not vcsStatus() since the index can only be guaranteed
2447         // to be up to date if the file has been explicitly opened, which is not the case when
2448         // doing a search and replace as a part of a refactoring.
2449         if (managesFile(QFileInfo(fileName).absolutePath(), fileName)) {
2450             // Checkout is the only option for managed files in dynamic views
2451             return IVersionControl::OpenMandatory;
2452         } else {
2453             // Not managed files can be edited without noticing the VCS
2454             return IVersionControl::NoOpen;
2455         }
2456 
2457     } else {
2458         return IVersionControl::OpenOptional; // Snapshot views supports Hijack and check out
2459     }
2460 }
2461 
vcsOpen(const QString & fileName)2462 bool ClearCasePluginPrivate::vcsOpen(const QString &fileName)
2463 {
2464     const QFileInfo fi(fileName);
2465     return vcsOpen(fi.absolutePath(), fi.fileName());
2466 }
2467 
settingsFlags() const2468 Core::IVersionControl::SettingsFlags ClearCasePluginPrivate::settingsFlags() const
2469 {
2470     SettingsFlags rc;
2471     if (m_settings.autoCheckOut)
2472         rc|= AutoOpen;
2473     return rc;
2474 }
2475 
vcsAdd(const QString & fileName)2476 bool ClearCasePluginPrivate::vcsAdd(const QString &fileName)
2477 {
2478     const QFileInfo fi(fileName);
2479     return vcsAdd(fi.absolutePath(), fi.fileName());
2480 }
2481 
vcsDelete(const QString & fileName)2482 bool ClearCasePluginPrivate::vcsDelete(const QString &fileName)
2483 {
2484     const QFileInfo fi(fileName);
2485     return vcsDelete(fi.absolutePath(), fi.fileName());
2486 }
2487 
vcsMove(const QString & from,const QString & to)2488 bool ClearCasePluginPrivate::vcsMove(const QString &from, const QString &to)
2489 {
2490     const QFileInfo ifrom(from);
2491     const QFileInfo ito(to);
2492     return vcsMove(ifrom.absolutePath(), ifrom.fileName(), ito.fileName());
2493 }
2494 
vcsAnnotate(const QString & file,int line)2495 void ClearCasePluginPrivate::vcsAnnotate(const QString &file, int line)
2496 {
2497     const QFileInfo fi(file);
2498     vcsAnnotateHelper(fi.absolutePath(), fi.fileName(), QString(), line);
2499 }
2500 
vcsOpenText() const2501 QString ClearCasePluginPrivate::vcsOpenText() const
2502 {
2503     return tr("Check &Out");
2504 }
2505 
vcsMakeWritableText() const2506 QString ClearCasePluginPrivate::vcsMakeWritableText() const
2507 {
2508     if (isDynamic())
2509         return QString();
2510     return tr("&Hijack");
2511 }
2512 
vcsTopic(const QString & directory)2513 QString ClearCasePluginPrivate::vcsTopic(const QString &directory)
2514 {
2515     return ccGetView(directory).name;
2516 }
2517 
vcsCreateRepository(const QString &)2518 bool ClearCasePluginPrivate::vcsCreateRepository(const QString &)
2519 {
2520     return false;
2521 }
2522 
2523 // ClearCasePlugin
2524 
~ClearCasePlugin()2525 ClearCasePlugin::~ClearCasePlugin()
2526 {
2527     delete dd;
2528     dd = nullptr;
2529 }
2530 
newActivity()2531 bool ClearCasePlugin::newActivity()
2532 {
2533     return dd->newActivity();
2534 }
2535 
activities(int * current)2536 const QList<QStringPair> ClearCasePlugin::activities(int *current)
2537 {
2538     return dd->activities(current);
2539 }
2540 
ccGetActiveVobs()2541 QStringList ClearCasePlugin::ccGetActiveVobs()
2542 {
2543     return dd->ccGetActiveVobs();
2544 }
2545 
refreshActivities()2546 void ClearCasePlugin::refreshActivities()
2547 {
2548     dd->refreshActivities();
2549 }
2550 
viewData()2551 const ViewData ClearCasePlugin::viewData()
2552 {
2553     return dd->m_viewData;
2554 }
2555 
setStatus(const QString & file,FileStatus::Status status,bool update)2556 void ClearCasePlugin::setStatus(const QString &file, FileStatus::Status status, bool update)
2557 {
2558     dd->setStatus(file, status, update);
2559 }
2560 
settings()2561 const ClearCaseSettings &ClearCasePlugin::settings()
2562 {
2563     return dd->m_settings;
2564 }
2565 
setSettings(const ClearCaseSettings & s)2566 void ClearCasePlugin::setSettings(const ClearCaseSettings &s)
2567 {
2568     dd->setSettings(s);
2569 }
2570 
statusMap()2571 QSharedPointer<StatusMap> ClearCasePlugin::statusMap()
2572 {
2573     return dd->m_statusMap;
2574 }
2575 
2576 #ifdef WITH_TESTS
2577 
testDiffFileResolving_data()2578 void ClearCasePlugin::testDiffFileResolving_data()
2579 {
2580     QTest::addColumn<QByteArray>("header");
2581     QTest::addColumn<QByteArray>("fileName");
2582 
2583     QTest::newRow("Modified") << QByteArray(
2584             "--- src/plugins/clearcase/clearcaseeditor.cpp@@/main/1\t2013-01-20 23:45:48.549615210 +0200\n"
2585             "+++ src/plugins/clearcase/clearcaseeditor.cpp@@/main/2\t2013-01-20 23:45:53.217604679 +0200\n"
2586             "@@ -58,6 +58,10 @@\n\n")
2587         << QByteArray("src/plugins/clearcase/clearcaseeditor.cpp");
2588 }
2589 
testDiffFileResolving()2590 void ClearCasePlugin::testDiffFileResolving()
2591 {
2592     VcsBaseEditorWidget::testDiffFileResolving(dd->diffEditorFactory);
2593 }
2594 
testLogResolving()2595 void ClearCasePlugin::testLogResolving()
2596 {
2597     QByteArray data(
2598                 "13-Sep.17:41   user1      create version \"src/plugins/clearcase/clearcaseeditor.h@@/main/branch1/branch2/9\" (baseline1, baseline2, ...)\n"
2599                 "22-Aug.14:13   user2      create version \"src/plugins/clearcase/clearcaseeditor.h@@/main/branch1/branch2/8\" (baseline3, baseline4, ...)\n"
2600                 );
2601     VcsBaseEditorWidget::testLogResolving(dd->logEditorFactory, data,
2602                             "src/plugins/clearcase/clearcaseeditor.h@@/main/branch1/branch2/9",
2603                             "src/plugins/clearcase/clearcaseeditor.h@@/main/branch1/branch2/8");
2604 }
2605 
initTestCase()2606 void ClearCasePlugin::initTestCase()
2607 {
2608     dd->m_tempFile = QDir::currentPath() + QLatin1String("/cc_file.cpp");
2609     FileSaver srcSaver(Utils::FilePath::fromString(dd->m_tempFile));
2610     srcSaver.write(QByteArray());
2611     srcSaver.finalize();
2612 }
2613 
cleanupTestCase()2614 void ClearCasePlugin::cleanupTestCase()
2615 {
2616     QVERIFY(QFile::remove(dd->m_tempFile));
2617 }
2618 
testFileStatusParsing_data()2619 void ClearCasePlugin::testFileStatusParsing_data()
2620 {
2621     QTest::addColumn<QString>("filename");
2622     QTest::addColumn<QString>("cleartoolLsLine");
2623     QTest::addColumn<int>("status");
2624 
2625     QTest::newRow("CheckedOut")
2626             << dd->m_tempFile
2627             << QString(dd->m_tempFile + QLatin1String("@@/main/branch1/CHECKEDOUT from /main/branch1/0  Rule: CHECKEDOUT"))
2628             << static_cast<int>(FileStatus::CheckedOut);
2629 
2630     QTest::newRow("CheckedIn")
2631             << dd->m_tempFile
2632             << QString(dd->m_tempFile + QLatin1String("@@/main/9  Rule: MY_LABEL_1.6.4 [-mkbranch branch1]"))
2633             << static_cast<int>(FileStatus::CheckedIn);
2634 
2635     QTest::newRow("Hijacked")
2636             << dd->m_tempFile
2637             << QString(dd->m_tempFile + QLatin1String("@@/main/9 [hijacked]        Rule: MY_LABEL_1.5.33 [-mkbranch myview1]"))
2638             << static_cast<int>(FileStatus::Hijacked);
2639 
2640 
2641     QTest::newRow("Missing")
2642             << dd->m_tempFile
2643             << QString(dd->m_tempFile + QLatin1String("@@/main/9 [loaded but missing]              Rule: MY_LABEL_1.5.33 [-mkbranch myview1]"))
2644             << static_cast<int>(FileStatus::Missing);
2645 }
2646 
testFileStatusParsing()2647 void ClearCasePlugin::testFileStatusParsing()
2648 {
2649     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2650 
2651     QFETCH(QString, filename);
2652     QFETCH(QString, cleartoolLsLine);
2653     QFETCH(int, status);
2654 
2655     ClearCaseSync ccSync(dd->m_statusMap);
2656     ccSync.verifyParseStatus(filename, cleartoolLsLine, static_cast<FileStatus::Status>(status));
2657 }
2658 
testFileNotManaged()2659 void ClearCasePlugin::testFileNotManaged()
2660 {
2661     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2662     ClearCaseSync ccSync(dd->m_statusMap);
2663     ccSync.verifyFileNotManaged();
2664 }
2665 
testFileCheckedOutDynamicView()2666 void ClearCasePlugin::testFileCheckedOutDynamicView()
2667 {
2668     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2669 
2670     ClearCaseSync ccSync(dd->m_statusMap);
2671     ccSync.verifyFileCheckedOutDynamicView();
2672 }
2673 
testFileCheckedInDynamicView()2674 void ClearCasePlugin::testFileCheckedInDynamicView()
2675 {
2676     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2677     ClearCaseSync ccSync(dd->m_statusMap);
2678     ccSync.verifyFileCheckedInDynamicView();
2679 }
2680 
testFileNotManagedDynamicView()2681 void ClearCasePlugin::testFileNotManagedDynamicView()
2682 {
2683     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2684     ClearCaseSync ccSync(dd->m_statusMap);
2685     ccSync.verifyFileNotManagedDynamicView();
2686 }
2687 
2688 namespace {
2689 /**
2690  * @brief Convenience class which also properly cleans up editors and temp files
2691  */
2692 class TestCase
2693 {
2694 public:
TestCase(const QString & fileName)2695     TestCase(const QString &fileName) :
2696         m_fileName(fileName)
2697     {
2698         ClearCasePluginPrivate::instance()->setFakeCleartool(true);
2699         VcsManager::clearVersionControlCache();
2700 
2701         FileSaver srcSaver(Utils::FilePath::fromString(fileName));
2702         srcSaver.write(QByteArray());
2703         srcSaver.finalize();
2704         m_editor = EditorManager::openEditor(fileName);
2705 
2706         QCoreApplication::processEvents(); // process any pending events
2707     }
2708 
dummyViewData() const2709     ViewData dummyViewData() const
2710     {
2711         ViewData viewData;
2712         viewData.name = QLatin1String("fake_view");
2713         viewData.root = QDir::currentPath();
2714         viewData.isUcm = false;
2715         return viewData;
2716     }
2717 
~TestCase()2718     ~TestCase()
2719     {
2720         EditorManager::closeDocuments({m_editor->document()}, false);
2721         QCoreApplication::processEvents(); // process any pending events
2722 
2723         QFile file(m_fileName);
2724         if (!file.isWritable()) // Windows can't delete read only files
2725             file.setPermissions(file.permissions() | QFile::WriteUser);
2726         QVERIFY(file.remove());
2727         ClearCasePluginPrivate::instance()->setFakeCleartool(false);
2728     }
2729 
2730 private:
2731     QString m_fileName;
2732     IEditor *m_editor;
2733 };
2734 }
2735 
testStatusActions_data()2736 void ClearCasePlugin::testStatusActions_data()
2737 {
2738     QTest::addColumn<int>("status");
2739     QTest::addColumn<bool>("checkOutAction");
2740     QTest::addColumn<bool>("undoCheckOutAction");
2741     QTest::addColumn<bool>("undoHijackAction");
2742     QTest::addColumn<bool>("checkInCurrentAction");
2743     QTest::addColumn<bool>("addFileAction");
2744     QTest::addColumn<bool>("checkInActivityAction");
2745     QTest::addColumn<bool>("diffActivityAction");
2746 
2747     QTest::newRow("Unknown")    << static_cast<int>(FileStatus::Unknown)
2748                                 << true  << true  << true  << true  << true  << false << false;
2749     QTest::newRow("CheckedOut") << static_cast<int>(FileStatus::CheckedOut)
2750                                 << false << true  << false << true  << false << false << false;
2751     QTest::newRow("CheckedIn")  << static_cast<int>(FileStatus::CheckedIn)
2752                                 << true  << false << false << false << false << false << false;
2753     QTest::newRow("NotManaged") << static_cast<int>(FileStatus::NotManaged)
2754                                 << false << false << false << false << true  << false << false;
2755 }
2756 
testStatusActions()2757 void ClearCasePlugin::testStatusActions()
2758 {
2759     const QString fileName = QDir::currentPath() + QLatin1String("/clearcase_file.cpp");
2760     TestCase testCase(fileName);
2761 
2762     dd->m_viewData = testCase.dummyViewData();
2763 
2764     QFETCH(int, status);
2765     auto tempStatus = static_cast<FileStatus::Status>(status);
2766 
2767     // special case: file should appear as "Unknown" since there is no entry in the index
2768     // and we don't want to explicitly set the status for this test case
2769     if (tempStatus != FileStatus::Unknown)
2770         dd->setStatus(fileName, tempStatus, true);
2771 
2772     QFETCH(bool, checkOutAction);
2773     QFETCH(bool, undoCheckOutAction);
2774     QFETCH(bool, undoHijackAction);
2775     QFETCH(bool, checkInCurrentAction);
2776     QFETCH(bool, addFileAction);
2777     QFETCH(bool, checkInActivityAction);
2778     QFETCH(bool, diffActivityAction);
2779 
2780     QCOMPARE(dd->m_checkOutAction->isEnabled(), checkOutAction);
2781     QCOMPARE(dd->m_undoCheckOutAction->isEnabled(), undoCheckOutAction);
2782     QCOMPARE(dd->m_undoHijackAction->isEnabled(), undoHijackAction);
2783     QCOMPARE(dd->m_checkInCurrentAction->isEnabled(), checkInCurrentAction);
2784     QCOMPARE(dd->m_addFileAction->isEnabled(), addFileAction);
2785     QCOMPARE(dd->m_checkInActivityAction->isEnabled(), checkInActivityAction);
2786     QCOMPARE(dd->m_diffActivityAction->isEnabled(), diffActivityAction);
2787 }
2788 
testVcsStatusDynamicReadonlyNotManaged()2789 void ClearCasePlugin::testVcsStatusDynamicReadonlyNotManaged()
2790 {
2791     // File is not in map, and is read-only
2792     ClearCasePluginPrivate::instance();
2793     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2794 
2795     const QString fileName = QDir::currentPath() + QLatin1String("/readonly_notmanaged_file.cpp");
2796 
2797     dd->m_viewData.isDynamic = true;
2798     TestCase testCase(fileName);
2799 
2800     QFile::setPermissions(fileName, QFile::ReadOwner |
2801                           QFile::ReadUser |
2802                           QFile::ReadGroup |
2803                           QFile::ReadOther);
2804 
2805     dd->m_viewData = testCase.dummyViewData();
2806     dd->m_viewData.isDynamic = true;
2807 
2808     QCOMPARE(dd->vcsStatus(fileName).status, FileStatus::NotManaged);
2809 
2810 }
2811 
testVcsStatusDynamicNotManaged()2812 void ClearCasePlugin::testVcsStatusDynamicNotManaged()
2813 {
2814     ClearCasePluginPrivate::instance();
2815     dd->m_statusMap = QSharedPointer<StatusMap>(new StatusMap);
2816 
2817     const QString fileName = QDir::currentPath() + QLatin1String("/notmanaged_file.cpp");
2818 
2819     dd->m_viewData.isDynamic = true;
2820     TestCase testCase(fileName);
2821 
2822     dd->m_viewData = testCase.dummyViewData();
2823     dd->m_viewData.isDynamic = true;
2824 
2825     QCOMPARE(dd->vcsStatus(fileName).status, FileStatus::NotManaged);
2826 }
2827 #endif
2828 
2829 } // namespace Internal
2830 } // namespace ClearCase
2831 
2832 #include "clearcaseplugin.moc"
2833