Newer
Older
/****************************************************************************
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "gitconstants.h"
#include "gitplugin.h"
#include "gitsubmiteditor.h"
#include "gitversioncontrol.h"
#include <vcsbase/submitfilemodel.h>
#include <coreplugin/vcsmanager.h>
#include <coreplugin/id.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/coreconstants.h>
#include <utils/synchronousprocess.h>
#include <vcsbase/vcsbaseeditorparameterwidget.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <vcsbase/vcsbaseplugin.h>
#include <diffeditor/diffeditorconstants.h>
#include <diffeditor/diffeditorcontroller.h>
#include <diffeditor/diffeditordocument.h>
#include <diffeditor/diffeditormanager.h>
#include <QSignalMapper>
#include <QToolButton>
#include <QTextCodec>
static const char GIT_DIRECTORY[] = ".git";
static const char graphLogFormatC[] = "%h %d %an %s %ci";
static const char HEAD[] = "HEAD";
static const char noColorOption[] = "--no-color";
static const char decorateOption[] = "--decorate";
namespace Git {
namespace Internal {
// Suppress git diff warnings about "LF will be replaced by CRLF..." on Windows.
static inline unsigned diffExecutionFlags()
{
return Utils::HostOsInfo::isWindowsHost() ? unsigned(VcsBase::VcsBasePlugin::SuppressStdErrInLogWindow) : 0u;
}
class GitDiffSwitcher : public QObject
{
Q_OBJECT
public:
enum DiffType {
DiffRepository,
DiffFile,
DiffFileList,
DiffProjectList,
DiffBranch,
DiffShow
};
GitDiffSwitcher(Core::IDocument *parentDocument, GitClient *gitClient);
void setWorkingDirectory(const QString &workingDir) { m_workingDirectory = workingDir; }
void setDiffType(DiffType type) { m_diffType = type; }
void setFileName(const QString &fileName) { m_fileName = fileName; }
void setFileList(const QStringList &stagedFiles, const QStringList &unstagedFiles)
{
m_stagedFiles = stagedFiles;
m_unstagedFiles = unstagedFiles;
}
void setProjectList(const QStringList &projectFiles) { m_projectFiles = projectFiles; }
void setBranchName(const QString &branchName) { m_branchName = branchName; }
void setId(const QString &id) { m_id = id; }
void setDisplayName(const QString &displayName) { m_displayName = displayName; }
void setBaseArguments(const QStringList &args) { m_baseArguments = args; }
private slots:
void slotEditorOpened(Core::IEditor *editor);
void slotEditorClosed(Core::IEditor *editor);
void execute(QObject *editor);
void attachAction(Core::IEditor *editor);
QString actionText();
Core::IDocument *m_document;
GitClient *m_gitClient;
QString m_workingDirectory;
DiffType m_diffType;
QString m_fileName;
QStringList m_stagedFiles;
QStringList m_unstagedFiles;
QStringList m_projectFiles;
QString m_branchName;
QString m_id;
QString m_displayName;
QStringList m_baseArguments;
QSignalMapper *m_signalMapper;
QMap<QObject *, bool> m_editorToUsingSideBySideDiffEditor;
GitDiffSwitcher::GitDiffSwitcher(Core::IDocument *parentDocument, GitClient *gitClient)
: QObject(parentDocument),
m_document(parentDocument),
m_gitClient(gitClient),
m_signalMapper(new QSignalMapper(this))
{
Core::DocumentModel *documentModel = Core::EditorManager::documentModel();
QList<Core::IEditor *> editors = documentModel->editorsForDocument(m_document);
for (int i = 0; i < editors.count(); i++)
attachAction(editors.at(i));
// must be queued connection because execute() removes the editor & tool bar that the action was added to
connect(m_signalMapper, SIGNAL(mapped(QObject*)), this, SLOT(execute(QObject*)), Qt::QueuedConnection);
connect(Core::EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)),
this, SLOT(slotEditorOpened(Core::IEditor*)));
connect(Core::EditorManager::instance(), SIGNAL(editorAboutToClose(Core::IEditor*)),
this, SLOT(slotEditorClosed(Core::IEditor*)));
}
void GitDiffSwitcher::attachAction(Core::IEditor *editor)
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
if (m_editorToUsingSideBySideDiffEditor.contains(editor))
return;
const bool usingSideBySideDiffEditor = m_gitClient->settings()->boolValue(GitSettings::useDiffEditorKey);
QIcon actionIcon = usingSideBySideDiffEditor
? QIcon(QLatin1String(Core::Constants::ICON_TEXT_DIFF))
: QIcon(QLatin1String(Core::Constants::ICON_SIDE_BY_SIDE_DIFF));
const QString actionToolTip = usingSideBySideDiffEditor
? tr("Switch to Text Diff Editor")
: tr("Switch to Side By Side Diff Editor");
QAction *switchAction = new QAction(actionIcon, actionToolTip, editor);
editor->toolBar()->addAction(switchAction);
connect(switchAction, SIGNAL(triggered()),
m_signalMapper, SLOT(map()));
m_signalMapper->setMapping(switchAction, editor);
m_editorToUsingSideBySideDiffEditor.insert(editor, usingSideBySideDiffEditor);
}
void GitDiffSwitcher::slotEditorOpened(Core::IEditor *editor)
{
Core::IDocument *document = editor->document();
if (document != m_document)
return;
attachAction(editor);
}
void GitDiffSwitcher::slotEditorClosed(Core::IEditor *editor)
{
Core::IDocument *document = editor->document();
if (document != m_document)
return;
m_editorToUsingSideBySideDiffEditor.remove(editor);
}
void GitDiffSwitcher::execute(QObject *editor)
{
bool usingSideBySideEditor = !m_editorToUsingSideBySideDiffEditor.value(editor);
m_editorToUsingSideBySideDiffEditor[editor] = usingSideBySideEditor;
m_gitClient->settings()->setValue(GitSettings::useDiffEditorKey, usingSideBySideEditor);
Core::IEditor *ieditor = qobject_cast<Core::IEditor*>(editor);
Core::EditorManager::activateEditor(ieditor);
switch (m_diffType) {
case DiffRepository:
m_gitClient->diff(m_workingDirectory, QStringList(), QStringList());
m_gitClient->diff(m_workingDirectory, m_fileName);
m_gitClient->diff(m_workingDirectory, m_unstagedFiles, m_stagedFiles);
m_gitClient->diff(m_workingDirectory, m_projectFiles, QStringList());
m_gitClient->diffBranch(m_workingDirectory, m_baseArguments, m_branchName);
m_gitClient->show(m_fileName, m_id, m_baseArguments, m_displayName);
break;
default:
break;
}
Core::EditorManager::closeEditor(ieditor, false);
class GitDiffHandler : public QObject
{
Q_OBJECT
public:
enum RevisionType {
WorkingTree,
Index,
Other
};
struct Revision {
Revision() : type(WorkingTree) { }
Revision(RevisionType t) : type(t) { }
Revision(RevisionType t, const QString &i) : type(t), id(i) { }
RevisionType type;
QString id; // can be sha or HEAD
QString infoText() const
{
switch (type) {
case WorkingTree: return tr("Working tree");
case Index: return tr("Index");
default: return id;
}
}
};
GitDiffHandler(DiffEditor::DiffEditorController *editorController,
const QString &workingDirectory,
const QProcessEnvironment &environment,
int timeout);
// index -> working tree
void diffFile(const QString &fileName);
// stagedFileNames: HEAD -> index
// unstagedFileNames: index -> working tree
void diffFiles(const QStringList &stagedFileNames, const QStringList &unstagedFileNames);
// index -> working tree
void diffProjects(const QStringList &projectPaths);
// index -> working tree
void diffRepository();
// branch HEAD -> working tree
void diffBranch(const QString &branchName);
// id^ -> id
void show(const QString &id);
void slotShowDescriptionReceived(const QString &data);
void slotFileListReceived(const QString &fileList);
void slotFileContentsReceived(const QString &contents);
void collectShowDescription(const QString &id);
void collectFilesList(const QStringList &additionalArguments);
void prepareForCollection();
void collectFilesContents();
void feedEditor();
QString workingTreeContents(const QString &fileName) const;
QPointer<DiffEditor::DiffEditorController> m_editorController;
const QString m_gitPath;
const QString m_workingDirectory;
const QProcessEnvironment m_processEnvironment;
const int m_timeout;
const QString m_waitMessage;
struct RevisionRange {
RevisionRange() { }
RevisionRange(const Revision &b, const Revision &e) : begin(b), end(e) { }
Revision begin;
Revision end;
// filename, revision range
QMap<QString, QList<RevisionRange> > m_requestedRevisionRanges;
// filename, revision, dummy
QMap<QString, QMap<Revision, bool> > m_pendingRevisions;
// filename, revision, contents
QMap<QString, QMap<Revision, QString> > m_collectedRevisions;
RevisionRange m_requestedRevisionRange;
inline bool operator<(const GitDiffHandler::Revision &rev1, const GitDiffHandler::Revision &rev2)
{
if (rev1.type != rev2.type)
return rev1.type < rev2.type;
return rev1.id < rev2.id;
}
GitDiffHandler::GitDiffHandler(DiffEditor::DiffEditorController *editorController,
const QString &workingDirectory,
const QProcessEnvironment &environment,
int timeout)
m_workingDirectory(workingDirectory),
m_processEnvironment(environment),
m_timeout(timeout),
m_waitMessage(tr("Waiting for data..."))
{
}
void GitDiffHandler::diffFile(const QString &fileName)
{
m_requestedRevisionRange = RevisionRange(
Revision(WorkingTree));
collectFilesList(QStringList() << QLatin1String("--") << fileName);
}
void GitDiffHandler::diffFiles(const QStringList &stagedFileNames, const QStringList &unstagedFileNames)
{
RevisionRange stagedRange = RevisionRange(
Revision(Other, QLatin1String(HEAD)),
RevisionRange unstagedRange = RevisionRange(
Revision(Index),
Revision(WorkingTree));
for (int i = 0; i < stagedFileNames.count(); i++)
m_requestedRevisionRanges[stagedFileNames.at(i)].append(stagedRange);
for (int i = 0; i < unstagedFileNames.count(); i++)
m_requestedRevisionRanges[unstagedFileNames.at(i)].append(unstagedRange);
prepareForCollection();
collectFilesContents();
}
void GitDiffHandler::diffProjects(const QStringList &projectPaths)
{
m_requestedRevisionRange = RevisionRange(
Revision(WorkingTree));
collectFilesList(QStringList() << QLatin1String("--") << projectPaths);
}
void GitDiffHandler::diffRepository()
{
m_requestedRevisionRange = RevisionRange(
Revision(WorkingTree));
collectFilesList(QStringList());
}
void GitDiffHandler::diffBranch(const QString &branchName)
{
m_requestedRevisionRange = RevisionRange(
Revision(Other, branchName),
Revision(WorkingTree));
collectFilesList(QStringList() << branchName);
}
void GitDiffHandler::show(const QString &id)
{
Revision begin(Other, id + QLatin1Char('^'));
Revision end(Other, id);
m_requestedRevisionRange = RevisionRange(begin, end);
collectShowDescription(id);
}
void GitDiffHandler::collectShowDescription(const QString &id)
{
if (m_editorController.isNull()) {
deleteLater();
}
m_editorController->clear(m_waitMessage);
VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment);
command->setCodec(GitPlugin::instance()->gitClient()->encoding(m_workingDirectory,
"i18n.commitEncoding"));
connect(command, SIGNAL(output(QString)), this, SLOT(slotShowDescriptionReceived(QString)));
arguments << QLatin1String("show") << QLatin1String("-s")
<< QLatin1String(noColorOption) << QLatin1String(decorateOption) << id;
command->addJob(arguments, m_timeout);
command->execute();
}
void GitDiffHandler::slotShowDescriptionReceived(const QString &description)
if (m_editorController.isNull()) {
deleteLater();
m_editorController->setDescription(GitPlugin::instance()->gitClient()->
extendedShowDescription(m_workingDirectory, description));
collectFilesList(QStringList()
<< m_requestedRevisionRange.begin.id
<< m_requestedRevisionRange.end.id);
void GitDiffHandler::collectFilesList(const QStringList &additionalArguments)
{
if (m_editorController.isNull()) {
deleteLater();
}
m_editorController->clear(m_waitMessage);
VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment);
command->setCodec(Core::EditorManager::defaultTextCodec());
connect(command, SIGNAL(output(QString)), this, SLOT(slotFileListReceived(QString)));
QStringList arguments;
arguments << QLatin1String("diff") << QLatin1String("--name-only") << additionalArguments;
command->addJob(arguments, m_timeout);
command->addFlags(diffExecutionFlags());
command->execute();
}
void GitDiffHandler::slotFileListReceived(const QString &fileList)
if (m_editorController.isNull()) {
deleteLater();
QStringList fileNames = fileList.split(QLatin1Char('\n'), QString::SkipEmptyParts);
fileNames.removeDuplicates();
for (int i = 0; i < fileNames.count(); i++)
m_requestedRevisionRanges[fileNames.at(i)].append(m_requestedRevisionRange);
prepareForCollection();
collectFilesContents();
}
void GitDiffHandler::prepareForCollection()
QMap<QString, QList<RevisionRange> >::const_iterator it
= m_requestedRevisionRanges.constBegin();
QMap<QString, QList<RevisionRange> >::const_iterator itEnd
= m_requestedRevisionRanges.constEnd();
while (it != itEnd) {
const QString fileName = it.key();
const QList<RevisionRange> &ranges = it.value();
for (int i = 0; i < ranges.count(); i++) {
const RevisionRange &range = ranges.at(i);
m_pendingRevisions[fileName][range.begin] = false;
m_pendingRevisions[fileName][range.end] = false;
}
void GitDiffHandler::collectFilesContents()
{
QMap<QString, QMap<Revision, bool> >::iterator itFile
= m_pendingRevisions.begin();
QMap<QString, QMap<Revision, bool> >::iterator itFileEnd
= m_pendingRevisions.end();
while (itFile != itFileEnd) {
const QString fileName = itFile.key();
QMap<Revision, bool> &revisions = itFile.value();
QMap<Revision, bool>::iterator itRev
= revisions.begin();
QMap<Revision, bool>::iterator itRevEnd
= revisions.end();
while (itRev != itRevEnd) {
const Revision revision = itRev.key();
if (revision.type == WorkingTree) {
// collect file here
m_collectedRevisions[fileName][revision] = workingTreeContents(fileName);
itRev = revisions.erase(itRev); // iterate to the next revision
} else {
// prepare job here
VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment);
command->setCodec(Core::EditorManager::defaultTextCodec());
connect(command, SIGNAL(output(QString)), this, SLOT(slotFileContentsReceived(QString)));
QString revisionArgument = (revision.type == Other)
? revision.id : QString();
revisionArgument += QLatin1Char(':');
QStringList arguments;
arguments << QLatin1String("show") << revisionArgument + fileName;
command->addJob(arguments, m_timeout);
command->execute();
return;
}
}
itFile = m_pendingRevisions.erase(itFile); // iterate to the next file
void GitDiffHandler::slotFileContentsReceived(const QString &contents)
if (m_editorController.isNull()) {
deleteLater();
QMap<QString, QMap<Revision, bool> >::iterator itFile
= m_pendingRevisions.begin();
QMap<QString, QMap<Revision, bool> >::iterator itFileEnd
= m_pendingRevisions.end();
if (itFile != itFileEnd) {
const QString fileName = itFile.key();
QMap<Revision, bool> &revisions = itFile.value();
QMap<Revision, bool>::iterator itRev
= revisions.begin();
QMap<Revision, bool>::iterator itRevEnd
= revisions.end();
if (itRev != itRevEnd) {
m_collectedRevisions[fileName][itRev.key()] = contents;
itRev = revisions.erase(itRev);
if (revisions.isEmpty())
m_pendingRevisions.erase(itFile);
}
}
collectFilesContents();
}
void GitDiffHandler::feedEditor()
{
if (m_editorController.isNull()) {
deleteLater();
return;
}
QList<DiffEditor::DiffEditorController::DiffFilesContents> list;
QMap<QString, QList<RevisionRange> >::const_iterator itFile
= m_requestedRevisionRanges.constBegin();
QMap<QString, QList<RevisionRange> >::const_iterator itFileEnd
= m_requestedRevisionRanges.constEnd();
while (itFile != itFileEnd) {
const QString fileName = itFile.key();
const QList<RevisionRange> &ranges = itFile.value();
for (int i = 0; i < ranges.count(); i++) {
const Revision leftRevision = ranges.at(i).begin;
const Revision rightRevision = ranges.at(i).end;
DiffEditor::DiffEditorController::DiffFilesContents dfc;
dfc.leftFileInfo = DiffEditor::DiffEditorController::DiffFileInfo(fileName, leftRevision.infoText());
dfc.leftText = m_collectedRevisions[fileName][leftRevision];
dfc.rightFileInfo = DiffEditor::DiffEditorController::DiffFileInfo(fileName, rightRevision.infoText());
dfc.rightText = m_collectedRevisions[fileName][rightRevision];
m_editorController->setDiffContents(list, m_workingDirectory);
deleteLater();
}
QString GitDiffHandler::workingTreeContents(const QString &fileName) const
{
QDir workingDir(m_workingDirectory);
QString absoluteFileName = workingDir.absoluteFilePath(fileName);
QFile file(absoluteFileName);
return Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll());
return QString();
}
///////////////////////////////////////////////////////////
class BaseGitDiffArgumentsWidget : public VcsBase::VcsBaseEditorParameterWidget
BaseGitDiffArgumentsWidget(GitClient *client, const QString &directory,
QTC_ASSERT(!directory.isEmpty(), return);
QTC_ASSERT(m_client, return);
m_patienceButton = addToggleButton(QLatin1String("--patience"), tr("Patience"),
tr("Use the patience algorithm for calculating the differences."));
mapSetting(m_patienceButton, client->settings()->boolPointer(GitSettings::diffPatienceKey));
m_ignoreWSButton = addToggleButton(QLatin1String("--ignore-space-change"), tr("Ignore Whitespace"),
tr("Ignore whitespace only changes."));
mapSetting(m_ignoreWSButton, m_client->settings()->boolPointer(GitSettings::ignoreSpaceChangesInDiffKey));
QString m_workingDirectory;
GitClient *m_client;
QToolButton *m_patienceButton;
QToolButton *m_ignoreWSButton;
};
class GitCommitDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
GitCommitDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory,
const QStringList &unstaged, const QStringList &staged) :
BaseGitDiffArgumentsWidget(client, directory, QStringList())
{
setFileNames(unstaged, staged);
}
void setFileNames(const QStringList &unstaged, const QStringList &staged)
{
m_unstagedFileNames = unstaged;
m_stagedFileNames = staged;
}
m_client->diff(m_workingDirectory, m_unstagedFileNames, m_stagedFileNames);
QStringList m_unstagedFileNames;
QStringList m_stagedFileNames;
};
class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
GitFileDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory,
const QString &file) :
BaseGitDiffArgumentsWidget(client, directory, QStringList()),
m_client->diff(m_workingDirectory, m_fileName);
}
private:
const QString m_fileName;
};
class GitBranchDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
GitBranchDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory,
const QStringList &args, const QString &branch) :
BaseGitDiffArgumentsWidget(client, directory, args),
m_client->diffBranch(m_workingDirectory, baseArguments(), m_branchName);
}
private:
const QString m_branchName;
};
class GitShowArgumentsWidget : public BaseGitDiffArgumentsWidget
GitShowArgumentsWidget(Git::Internal::GitClient *client,
const QString &directory,
const QStringList &args,
const QString &id) :
BaseGitDiffArgumentsWidget(client, directory, args),
m_client(client),
m_workingDirectory(directory),
QList<ComboBoxItem> prettyChoices;
prettyChoices << ComboBoxItem(tr("oneline"), QLatin1String("oneline"))
<< ComboBoxItem(tr("short"), QLatin1String("short"))
<< ComboBoxItem(tr("medium"), QLatin1String("medium"))
<< ComboBoxItem(tr("full"), QLatin1String("full"))
<< ComboBoxItem(tr("fuller"), QLatin1String("fuller"))
<< ComboBoxItem(tr("email"), QLatin1String("email"))
<< ComboBoxItem(tr("raw"), QLatin1String("raw"));
mapSetting(addComboBox(QStringList(QLatin1String("--pretty=%1")), prettyChoices),
m_client->settings()->intPointer(GitSettings::showPrettyFormatKey));
m_client->show(m_workingDirectory, m_id, baseArguments());
GitClient *m_client;
QString m_workingDirectory;
class GitBlameArgumentsWidget : public VcsBase::VcsBaseEditorParameterWidget
GitBlameArgumentsWidget(Git::Internal::GitClient *client,
const QString &directory,
const QStringList &args,
const QString &revision, const QString &fileName) :
m_client(client),
m_workingDirectory(directory),
m_revision(revision),
m_fileName(fileName)
{
mapSetting(addToggleButton(QString(), tr("Omit Date"),
tr("Hide the date of a change from the output.")),
m_client->settings()->boolPointer(GitSettings::omitAnnotationDateKey));
mapSetting(addToggleButton(QLatin1String("-w"), tr("Ignore Whitespace"),
tr("Ignore whitespace only changes.")),
m_client->settings()->boolPointer(GitSettings::ignoreSpaceChangesInBlameKey));
int line = -1;
if (m_editor)
line = m_editor->lineNumberOfCurrentEditor();
m_client->blame(m_workingDirectory, baseArguments(), m_fileName, m_revision, line);
GitClient *m_client;
QString m_workingDirectory;
QString m_revision;
QString m_fileName;
};
class GitLogArgumentsWidget : public BaseGitDiffArgumentsWidget
{
Q_OBJECT
public:
GitLogArgumentsWidget(Git::Internal::GitClient *client,
const QString &directory,
bool enableAnnotationContextMenu,
const QStringList &args,
BaseGitDiffArgumentsWidget(client, directory, args),
m_client(client),
m_workingDirectory(directory),
m_enableAnnotationContextMenu(enableAnnotationContextMenu)
QToolButton *diffButton = addToggleButton(QLatin1String("--patch"), tr("Show Diff"),
mapSetting(diffButton, m_client->settings()->boolPointer(GitSettings::logDiffKey));
connect(diffButton, SIGNAL(toggled(bool)), m_patienceButton, SLOT(setVisible(bool)));
connect(diffButton, SIGNAL(toggled(bool)), m_ignoreWSButton, SLOT(setVisible(bool)));
m_patienceButton->setVisible(diffButton->isChecked());
m_ignoreWSButton->setVisible(diffButton->isChecked());
QStringList graphArguments(QLatin1String("--graph"));
graphArguments << QLatin1String("--oneline") << QLatin1String("--topo-order");
graphArguments << (QLatin1String("--pretty=format:") + QLatin1String(graphLogFormatC));
QToolButton *graphButton = addToggleButton(graphArguments, tr("Graph"),
tr("Show textual graph log."));
mapSetting(graphButton, m_client->settings()->boolPointer(GitSettings::graphLogKey));
void setFileName(const QString &fileNames)
}
void executeCommand()
{
m_client->log(m_workingDirectory, m_fileName, m_enableAnnotationContextMenu, baseArguments());
}
private:
GitClient *m_client;
QString m_workingDirectory;
bool m_enableAnnotationContextMenu;
class ConflictHandler : public QObject
{
Q_OBJECT
public:
ConflictHandler(VcsBase::Command *parentCommand,
const QString &command = QString())
m_workingDirectory(workingDirectory),
m_command(command)
{
parentCommand->addFlags(VcsBasePlugin::ExpectRepoChanges);
connect(parentCommand, SIGNAL(output(QString)), this, SLOT(readStdOut(QString)));
connect(parentCommand, SIGNAL(errorText(QString)), this, SLOT(readStdErr(QString)));
}
// If interactive rebase editor window is closed, plugin is terminated
// but referenced here when the command ends
if (GitPlugin *plugin = GitPlugin::instance()) {
GitClient *client = plugin->gitClient();
if (m_commit.isEmpty() && m_files.isEmpty()) {
if (client->checkCommandInProgress(m_workingDirectory) == GitClient::NoCommand)
client->endStashScope(m_workingDirectory);
} else {
client->handleMergeConflicts(m_workingDirectory, m_commit, m_files, m_command);
}
public slots:
void readStdOut(const QString &data)
{
static QRegExp patchFailedRE(QLatin1String("Patch failed at ([^\\n]*)"));
static QRegExp conflictedFilesRE(QLatin1String("Merge conflict in ([^\\n]*)"));
if (patchFailedRE.indexIn(data) != -1)
m_commit = patchFailedRE.cap(1);
int fileIndex = -1;
while ((fileIndex = conflictedFilesRE.indexIn(data, fileIndex + 1)) != -1) {
m_files.append(conflictedFilesRE.cap(1));
}
}
void readStdErr(const QString &data)
{
static QRegExp couldNotApplyRE(QLatin1String("[Cc]ould not (?:apply|revert) ([^\\n]*)"));
if (couldNotApplyRE.indexIn(data) != -1)
m_commit = couldNotApplyRE.cap(1);
}
private:
QString m_workingDirectory;
QString m_command;
QString m_commit;
class ProgressParser : public VcsBase::ProgressParser
{
public:
ProgressParser() :
m_progressExp(QLatin1String("\\((\\d+)/(\\d+)\\)")) // e.g. Rebasing (7/42)
{
}
protected:
void parseProgress(const QString &text)
{
if (m_progressExp.lastIndexIn(text) != -1)
setProgressAndMaximum(m_progressExp.cap(1).toInt(), m_progressExp.cap(2).toInt());
}
private:
QRegExp m_progressExp;
};
Core::IEditor *locateEditor(const char *property, const QString &entry)
foreach (Core::IDocument *document, Core::EditorManager::documentModel()->openedDocuments())
if (document->property(property).toString() == entry)
return Core::EditorManager::documentModel()->editorsForDocument(document).first();
// Return converted command output, remove '\r' read on Windows
static inline QString commandOutputFromLocal8Bit(const QByteArray &a)
{
return Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(a));
}
// Return converted command output split into lines
static inline QStringList commandOutputLinesFromLocal8Bit(const QByteArray &a)
{
QString output = commandOutputFromLocal8Bit(a);
const QChar newLine = QLatin1Char('\n');
if (output.endsWith(newLine))
output.truncate(output.size() - 1);
if (output.isEmpty())
return QStringList();
return output.split(newLine);
}
static inline QString msgRepositoryNotFound(const QString &dir)
{
return GitClient::tr("Cannot determine the repository for \"%1\".").arg(dir);
}
static inline QString msgParseFilesFailed()
{
return GitClient::tr("Cannot parse the file output.");
}
static inline QString msgCannotLaunch(const QString &binary)
{
return GitClient::tr("Cannot launch \"%1\".").arg(QDir::toNativeSeparators(binary));
}
static inline QString currentDocumentPath()
{
if (Core::IDocument *document= Core::EditorManager::currentDocument())
return QFileInfo(document->filePath()).path();
return QString();
static inline QStringList statusArguments()
{
return QStringList() << QLatin1String("-c") << QLatin1String("color.status=false")
<< QLatin1String("status");
}
static inline void msgCannotRun(const QString &message, QString *errorMessage)
{
if (errorMessage)