1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Hugues Delorme
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "bazaarclient.h"
27 #include "constants.h"
28 
29 #include <vcsbase/vcsbaseplugin.h>
30 #include <vcsbase/vcsoutputwindow.h>
31 #include <vcsbase/vcsbaseeditorconfig.h>
32 
33 #include <utils/hostosinfo.h>
34 
35 #include <QDir>
36 #include <QFileInfo>
37 #include <QRegularExpression>
38 #include <QTextStream>
39 #include <QDebug>
40 
41 using namespace Utils;
42 using namespace VcsBase;
43 
44 namespace Bazaar {
45 namespace Internal {
46 
47 // Parameter widget controlling whitespace diff mode, associated with a parameter
48 class BazaarDiffConfig : public VcsBaseEditorConfig
49 {
50     Q_OBJECT
51 public:
BazaarDiffConfig(BazaarSettings & settings,QToolBar * toolBar)52     BazaarDiffConfig(BazaarSettings &settings, QToolBar *toolBar) :
53         VcsBaseEditorConfig(toolBar)
54     {
55         mapSetting(addToggleButton("-w", tr("Ignore Whitespace")),
56                    &settings.diffIgnoreWhiteSpace);
57         mapSetting(addToggleButton("-B", tr("Ignore Blank Lines")),
58                    &settings.diffIgnoreBlankLines);
59     }
60 
arguments() const61     QStringList arguments() const override
62     {
63         QStringList args;
64         // Bazaar wants "--diff-options=-w -B.."
65         const QStringList formatArguments = VcsBaseEditorConfig::arguments();
66         if (!formatArguments.isEmpty()) {
67             const QString a = "--diff-options=" + formatArguments.join(' ');
68             args.append(a);
69         }
70         return args;
71     }
72 };
73 
74 class BazaarLogConfig : public VcsBaseEditorConfig
75 {
76     Q_OBJECT
77 public:
BazaarLogConfig(BazaarSettings & settings,QToolBar * toolBar)78     BazaarLogConfig(BazaarSettings &settings, QToolBar *toolBar) :
79         VcsBaseEditorConfig(toolBar)
80     {
81         mapSetting(addToggleButton("--verbose", tr("Verbose"),
82                                    tr("Show files changed in each revision.")),
83                    &settings.logVerbose);
84         mapSetting(addToggleButton("--forward", tr("Forward"),
85                                    tr("Show from oldest to newest.")),
86                    &settings.logForward);
87         mapSetting(addToggleButton("--include-merges", tr("Include Merges"),
88                                    tr("Show merged revisions.")),
89                    &settings.logIncludeMerges);
90 
91         const QList<ChoiceItem> logChoices = {
92             {tr("Detailed"), "long"},
93             {tr("Moderately Short"), "short"},
94             {tr("One Line"), "line"},
95             {tr("GNU Change Log"), "gnu-changelog"}
96         };
97         mapSetting(addChoices(tr("Format"), { "--log-format=%1" }, logChoices),
98                    &settings.logFormat);
99     }
100 };
101 
BazaarClient(BazaarSettings * settings)102 BazaarClient::BazaarClient(BazaarSettings *settings) : VcsBaseClient(settings)
103 {
104     setDiffConfigCreator([settings](QToolBar *toolBar) {
105         return new BazaarDiffConfig(*settings, toolBar);
106     });
107     setLogConfigCreator([settings](QToolBar *toolBar) {
108         return new BazaarLogConfig(*settings, toolBar);
109     });
110 }
111 
synchronousBranchQuery(const QString & repositoryRoot) const112 BranchInfo BazaarClient::synchronousBranchQuery(const QString &repositoryRoot) const
113 {
114     QFile branchConfFile(repositoryRoot + QLatin1Char('/') +
115                          QLatin1String(Constants::BAZAARREPO) +
116                          QLatin1String("/branch/branch.conf"));
117     if (!branchConfFile.open(QIODevice::ReadOnly))
118         return BranchInfo(QString(), false);
119 
120     QTextStream ts(&branchConfFile);
121     QString branchLocation;
122     QString isBranchBound;
123     QRegularExpression branchLocationRx("bound_location\\s*=\\s*(.+)$");
124     QRegularExpression isBranchBoundRx("bound\\s*=\\s*(.+)$");
125     while (!ts.atEnd() && (branchLocation.isEmpty() || isBranchBound.isEmpty())) {
126         const QString line = ts.readLine();
127         QRegularExpressionMatch match = branchLocationRx.match(line);
128         if (match.hasMatch()) {
129             branchLocation = match.captured(1);
130         } else {
131             QRegularExpressionMatch match = isBranchBoundRx.match(line);
132             if (match.hasMatch())
133                 isBranchBound = match.captured(1);
134         }
135     }
136     if (isBranchBound.simplified().toLower() == QLatin1String("true"))
137         return BranchInfo(branchLocation, true);
138     return BranchInfo(repositoryRoot, false);
139 }
140 
141 //! Removes the last committed revision(s)
synchronousUncommit(const QString & workingDir,const QString & revision,const QStringList & extraOptions)142 bool BazaarClient::synchronousUncommit(const QString &workingDir,
143                                        const QString &revision,
144                                        const QStringList &extraOptions)
145 {
146     QStringList args;
147     args << QLatin1String("uncommit")
148          << QLatin1String("--force")   // Say yes to all questions
149          << QLatin1String("--verbose") // Will print out what is being removed
150          << revisionSpec(revision)
151          << extraOptions;
152 
153     QtcProcess proc;
154     vcsFullySynchronousExec(proc, workingDir, args);
155     VcsOutputWindow::append(proc.stdOut());
156     return proc.result() == QtcProcess::FinishedWithSuccess;
157 }
158 
commit(const QString & repositoryRoot,const QStringList & files,const QString & commitMessageFile,const QStringList & extraOptions)159 void BazaarClient::commit(const QString &repositoryRoot, const QStringList &files,
160                           const QString &commitMessageFile, const QStringList &extraOptions)
161 {
162     VcsBaseClient::commit(repositoryRoot, files, commitMessageFile,
163                           QStringList(extraOptions) << QLatin1String("-F") << commitMessageFile);
164 }
165 
annotate(const QString & workingDir,const QString & file,const QString & revision,int lineNumber,const QStringList & extraOptions)166 VcsBaseEditorWidget *BazaarClient::annotate(
167         const QString &workingDir, const QString &file, const QString &revision,
168         int lineNumber, const QStringList &extraOptions)
169 {
170     return VcsBaseClient::annotate(workingDir, file, revision, lineNumber,
171                                    QStringList(extraOptions) << QLatin1String("--long"));
172 }
173 
isVcsDirectory(const FilePath & fileName) const174 bool BazaarClient::isVcsDirectory(const FilePath &fileName) const
175 {
176     return fileName.isDir()
177             && !fileName.fileName().compare(Constants::BAZAARREPO, HostOsInfo::fileNameCaseSensitivity());
178 }
179 
findTopLevelForFile(const QFileInfo & file) const180 QString BazaarClient::findTopLevelForFile(const QFileInfo &file) const
181 {
182     const QString repositoryCheckFile =
183             QLatin1String(Constants::BAZAARREPO) + QLatin1String("/branch-format");
184     return file.isDir() ?
185                 VcsBase::findRepositoryForDirectory(file.absoluteFilePath(), repositoryCheckFile) :
186                 VcsBase::findRepositoryForDirectory(file.absolutePath(), repositoryCheckFile);
187 }
188 
managesFile(const QString & workingDirectory,const QString & fileName) const189 bool BazaarClient::managesFile(const QString &workingDirectory, const QString &fileName) const
190 {
191     QStringList args(QLatin1String("status"));
192     args << fileName;
193 
194     QtcProcess proc;
195     vcsFullySynchronousExec(proc, workingDirectory, args);
196     if (proc.result() != QtcProcess::FinishedWithSuccess)
197         return false;
198     return proc.rawStdOut().startsWith("unknown");
199 }
200 
view(const QString & source,const QString & id,const QStringList & extraOptions)201 void BazaarClient::view(const QString &source, const QString &id, const QStringList &extraOptions)
202 {
203     QStringList args(QLatin1String("log"));
204     args << QLatin1String("-p") << QLatin1String("-v") << extraOptions;
205     VcsBaseClient::view(source, id, args);
206 }
207 
vcsEditorKind(VcsCommandTag cmd) const208 Utils::Id BazaarClient::vcsEditorKind(VcsCommandTag cmd) const
209 {
210     switch (cmd) {
211     case AnnotateCommand:
212         return Constants::ANNOTATELOG_ID;
213     case DiffCommand:
214         return Constants::DIFFLOG_ID;
215     case LogCommand:
216         return Constants::FILELOG_ID;
217     default:
218         return Utils::Id();
219     }
220 }
221 
vcsCommandString(VcsCommandTag cmd) const222 QString BazaarClient::vcsCommandString(VcsCommandTag cmd) const
223 {
224     switch (cmd) {
225     case CloneCommand:
226         return QLatin1String("branch");
227     default:
228         return VcsBaseClient::vcsCommandString(cmd);
229     }
230 }
231 
exitCodeInterpreter(VcsCommandTag cmd) const232 ExitCodeInterpreter BazaarClient::exitCodeInterpreter(VcsCommandTag cmd) const
233 {
234     if (cmd == DiffCommand) {
235         return [](int code) {
236             return (code < 0 || code > 2) ? QtcProcess::FinishedWithError
237                                           : QtcProcess::FinishedWithSuccess;
238         };
239     }
240     return {};
241 }
242 
revisionSpec(const QString & revision) const243 QStringList BazaarClient::revisionSpec(const QString &revision) const
244 {
245     QStringList args;
246     if (!revision.isEmpty())
247         args << QLatin1String("-r") << revision;
248     return args;
249 }
250 
parseStatusLine(const QString & line) const251 BazaarClient::StatusItem BazaarClient::parseStatusLine(const QString &line) const
252 {
253     StatusItem item;
254     if (!line.isEmpty()) {
255         const QChar flagVersion = line[0];
256         if (flagVersion == QLatin1Char('+'))
257             item.flags = QLatin1String("Versioned");
258         else if (flagVersion == QLatin1Char('-'))
259             item.flags = QLatin1String("Unversioned");
260         else if (flagVersion == QLatin1Char('R'))
261             item.flags = QLatin1String(Constants::FSTATUS_RENAMED);
262         else if (flagVersion == QLatin1Char('?'))
263             item.flags = QLatin1String("Unknown");
264         else if (flagVersion == QLatin1Char('X'))
265             item.flags = QLatin1String("Nonexistent");
266         else if (flagVersion == QLatin1Char('C'))
267             item.flags = QLatin1String("Conflict");
268         else if (flagVersion == QLatin1Char('P'))
269             item.flags = QLatin1String("PendingMerge");
270 
271         const int lineLength = line.length();
272         if (lineLength >= 2) {
273             const QChar flagContents = line[1];
274             if (flagContents == QLatin1Char('N'))
275                 item.flags = QLatin1String(Constants::FSTATUS_CREATED);
276             else if (flagContents == QLatin1Char('D'))
277                 item.flags = QLatin1String(Constants::FSTATUS_DELETED);
278             else if (flagContents == QLatin1Char('K'))
279                 item.flags = QLatin1String("KindChanged");
280             else if (flagContents == QLatin1Char('M'))
281                 item.flags = QLatin1String(Constants::FSTATUS_MODIFIED);
282         }
283         if (lineLength >= 3) {
284             const QChar flagExec = line[2];
285             if (flagExec == QLatin1Char('*'))
286                 item.flags = QLatin1String("ExecuteBitChanged");
287         }
288         // The status string should be similar to "xxx file_with_changes"
289         // so just should take the file name part and store it
290         item.file = line.mid(4);
291     }
292     return item;
293 }
294 
295 } // namespace Internal
296 } // namespace Bazaar
297 
298 #include "bazaarclient.moc"
299