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