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