diff options
Diffstat (limited to 'src/plugins/git/gitclient.cpp')
-rw-r--r-- | src/plugins/git/gitclient.cpp | 1034 |
1 files changed, 700 insertions, 334 deletions
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 949705242c..124a0d63a4 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -44,6 +44,7 @@ #include <coreplugin/vcsmanager.h> #include <coreplugin/id.h> #include <coreplugin/iversioncontrol.h> +#include <coreplugin/coreconstants.h> #include <utils/hostosinfo.h> #include <utils/qtcassert.h> @@ -84,6 +85,100 @@ namespace Internal { using VcsBase::VcsBasePlugin; +class GitDiffSwitcher : public QObject +{ + Q_OBJECT + +public: + enum DiffType { + DiffRepository, + DiffFile, + DiffFileList, + DiffProjectList, + DiffBranch, + DiffShow + }; + + GitDiffSwitcher(Core::IEditor *parentEditor, GitClient *gitClient) + : QObject(parentEditor), + m_editor(parentEditor), + m_gitClient(gitClient) + { + m_usingDiffEditor = gitClient->settings()->boolValue(GitSettings::useDiffEditorKey); + QIcon actionIcon = m_usingDiffEditor + ? QIcon(QLatin1String(Core::Constants::ICON_TEXT_DIFF)) + : QIcon(QLatin1String(Core::Constants::ICON_SIDE_BY_SIDE_DIFF)); + + const QString actionToolTip = m_usingDiffEditor + ? tr("Switch to Text Diff Editor") + : tr("Switch to Side By Side Diff Editor"); + + QAction *switchAction = new QAction(actionIcon, actionToolTip, parentEditor); + parentEditor->toolBar()->addAction(switchAction); + connect(switchAction, SIGNAL(triggered()), this, SLOT(execute())); + } + + 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; } + +public slots: + void execute(); + +private: + Core::IEditor *m_editor; + GitClient *m_gitClient; + QString m_workingDirectory; + DiffType m_diffType; + bool m_usingDiffEditor; + QString m_fileName; + QStringList m_stagedFiles; + QStringList m_unstagedFiles; + QStringList m_projectFiles; + QString m_branchName; + QString m_id; + QString m_displayName; + QStringList m_baseArguments; +}; + +void GitDiffSwitcher::execute() +{ + m_gitClient->settings()->setValue(GitSettings::useDiffEditorKey, !m_usingDiffEditor); + switch (m_diffType) { + case DiffRepository: + m_gitClient->diff(m_workingDirectory, QStringList(), QStringList()); + break; + case DiffFile: + m_gitClient->diff(m_workingDirectory, m_fileName); + break; + case DiffFileList: + m_gitClient->diff(m_workingDirectory, m_unstagedFiles, m_stagedFiles); + break; + case DiffProjectList: + m_gitClient->diff(m_workingDirectory, m_projectFiles, QStringList()); + break; + case DiffBranch: + m_gitClient->diffBranch(m_workingDirectory, m_baseArguments, m_branchName); + break; + case DiffShow: + m_gitClient->show(m_fileName, m_id, m_baseArguments, m_displayName); + break; + default: + break; + } + Core::EditorManager::closeEditor(m_editor, false); +} + class GitDiffHandler : public QObject { Q_OBJECT @@ -132,9 +227,9 @@ public: void show(const QString &id); private slots: - void slotShowDescriptionReceived(const QByteArray &data); - void slotFileListReceived(const QByteArray &data); - void slotFileContentsReceived(const QByteArray &data); + void slotShowDescriptionReceived(const QString &data); + void slotFileListReceived(const QString &fileList); + void slotFileContentsReceived(const QString &contents); private: void collectShowDescription(const QString &id); @@ -259,23 +354,24 @@ void GitDiffHandler::collectShowDescription(const QString &id) return; m_editor->clear(m_waitMessage); VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); - connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(slotShowDescriptionReceived(QByteArray))); + command->setCodec(m_editor->editorWidget()->codec()); + connect(command, SIGNAL(output(QString)), this, SLOT(slotShowDescriptionReceived(QString))); QStringList arguments; - arguments << QLatin1String("show") << QLatin1String("-s") << QLatin1String("--format=fuller") + arguments << QLatin1String("show") << QLatin1String("-s") << QLatin1String(noColorOption) << QLatin1String(decorateOption) << id; command->addJob(arguments, m_timeout); command->execute(); } -void GitDiffHandler::slotShowDescriptionReceived(const QByteArray &data) +void GitDiffHandler::slotShowDescriptionReceived(const QString &description) { if (m_editor.isNull()) return; - const QString description = m_editor->editorWidget()->codec()->toUnicode(data).remove(QLatin1Char('\r')); - DiffEditor::DiffShowEditor *editor = qobject_cast<DiffEditor::DiffShowEditor *>(m_editor); - if (editor) - editor->setDescription(description); + if (editor) { + editor->setDescription(GitPlugin::instance()->gitClient()-> + extendedShowDescription(m_workingDirectory, description)); + } collectFilesList(QStringList() << m_requestedRevisionRange.begin.id @@ -288,19 +384,19 @@ void GitDiffHandler::collectFilesList(const QStringList &additionalArguments) return; m_editor->clear(m_waitMessage); VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); - connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(slotFileListReceived(QByteArray))); + command->setCodec(m_editor->editorWidget()->codec()); + connect(command, SIGNAL(output(QString)), this, SLOT(slotFileListReceived(QString))); QStringList arguments; arguments << QLatin1String("diff") << QLatin1String("--name-only") << additionalArguments; command->addJob(arguments, m_timeout); command->execute(); } -void GitDiffHandler::slotFileListReceived(const QByteArray &data) +void GitDiffHandler::slotFileListReceived(const QString &fileList) { if (m_editor.isNull()) return; - const QString fileList = m_editor->editorWidget()->codec()->toUnicode(data).remove(QLatin1Char('\r')); QStringList fileNames = fileList.split(QLatin1Char('\n'), QString::SkipEmptyParts); fileNames.removeDuplicates(); @@ -355,11 +451,13 @@ void GitDiffHandler::collectFilesContents() // prepare job here VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); - connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(slotFileContentsReceived(QByteArray))); + if (m_editor) + command->setCodec(m_editor->editorWidget()->codec()); + connect(command, SIGNAL(output(QString)), this, SLOT(slotFileContentsReceived(QString))); QString revisionArgument = (revision.type == Other) ? revision.id : QString(); - revisionArgument += QLatin1String(":./"); + revisionArgument += QLatin1Char(':'); QStringList arguments; arguments << QLatin1String("show") << revisionArgument + fileName; command->addJob(arguments, m_timeout); @@ -375,7 +473,7 @@ void GitDiffHandler::collectFilesContents() feedEditor(); } -void GitDiffHandler::slotFileContentsReceived(const QByteArray &data) +void GitDiffHandler::slotFileContentsReceived(const QString &contents) { if (m_editor.isNull()) return; @@ -392,7 +490,6 @@ void GitDiffHandler::slotFileContentsReceived(const QByteArray &data) QMap<Revision, bool>::iterator itRevEnd = revisions.end(); if (itRev != itRevEnd) { - const QString contents = m_editor->editorWidget()->codec()->toUnicode(data).remove(QLatin1Char('\r')); m_collectedRevisions[fileName][itRev.key()] = contents; itRev = revisions.erase(itRev); @@ -484,21 +581,26 @@ class GitCommitDiffArgumentsWidget : public BaseGitDiffArgumentsWidget public: GitCommitDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory, - const QStringList &args, const QStringList &unstaged, - const QStringList &staged) : - BaseGitDiffArgumentsWidget(client, directory, args), - m_unstagedFileNames(unstaged), - m_stagedFileNames(staged) - { } + 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; + } void executeCommand() { - m_client->diff(m_workingDirectory, arguments(), m_unstagedFileNames, m_stagedFileNames); + m_client->diff(m_workingDirectory, m_unstagedFileNames, m_stagedFileNames); } private: - const QStringList m_unstagedFileNames; - const QStringList m_stagedFileNames; + QStringList m_unstagedFileNames; + QStringList m_stagedFileNames; }; class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget @@ -506,14 +608,14 @@ class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget Q_OBJECT public: GitFileDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory, - const QStringList &args, const QString &file) : - BaseGitDiffArgumentsWidget(client, directory, args), + const QString &file) : + BaseGitDiffArgumentsWidget(client, directory, QStringList()), m_fileName(file) { } void executeCommand() { - m_client->diff(m_workingDirectory, arguments(), m_fileName); + m_client->diff(m_workingDirectory, m_fileName); } private: @@ -532,7 +634,7 @@ public: void executeCommand() { - m_client->diffBranch(m_workingDirectory, arguments(), m_branchName); + m_client->diffBranch(m_workingDirectory, baseArguments(), m_branchName); } private: @@ -567,7 +669,7 @@ public: void executeCommand() { - m_client->show(m_workingDirectory, m_id, arguments()); + m_client->show(m_workingDirectory, m_id, baseArguments()); } private: @@ -612,7 +714,7 @@ public: int line = -1; if (m_editor) line = m_editor->lineNumberOfCurrentEditor(); - m_client->blame(m_workingDirectory, arguments(), m_fileName, m_revision, line); + m_client->blame(m_workingDirectory, baseArguments(), m_fileName, m_revision, line); } private: @@ -632,12 +734,11 @@ public: const QString &directory, bool enableAnnotationContextMenu, const QStringList &args, - const QStringList &fileNames) : + const QString &fileName) : BaseGitDiffArgumentsWidget(client, directory, args), m_client(client), m_workingDirectory(directory), - m_enableAnnotationContextMenu(enableAnnotationContextMenu), - m_fileNames(fileNames) + m_enableAnnotationContextMenu(enableAnnotationContextMenu) { QTC_ASSERT(!directory.isEmpty(), return); QToolButton *diffButton = addToggleButton(QLatin1String("--patch"), tr("Show Diff"), @@ -653,18 +754,24 @@ public: QToolButton *graphButton = addToggleButton(graphArguments, tr("Graph"), tr("Show textual graph log.")); mapSetting(graphButton, m_client->settings()->boolPointer(GitSettings::graphLogKey)); + setFileName(fileName); + } + + void setFileName(const QString &fileNames) + { + m_fileName = fileNames; } void executeCommand() { - m_client->log(m_workingDirectory, m_fileNames, m_enableAnnotationContextMenu, arguments()); + m_client->log(m_workingDirectory, m_fileName, m_enableAnnotationContextMenu, baseArguments()); } private: GitClient *m_client; QString m_workingDirectory; bool m_enableAnnotationContextMenu; - QStringList m_fileNames; + QString m_fileName; }; class ConflictHandler : public QObject @@ -679,8 +786,8 @@ public: m_command(command) { if (parentCommand) { - parentCommand->setExpectChanges(true); - connect(parentCommand, SIGNAL(outputData(QByteArray)), this, SLOT(readStdOut(QByteArray))); + parentCommand->addFlags(VcsBasePlugin::ExpectRepoChanges); + connect(parentCommand, SIGNAL(output(QString)), this, SLOT(readStdOut(QString))); connect(parentCommand, SIGNAL(errorText(QString)), this, SLOT(readStdErr(QString))); } } @@ -700,7 +807,8 @@ public: } } - void readStdOutString(const QString &data) +public slots: + void readStdOut(const QString &data) { static QRegExp patchFailedRE(QLatin1String("Patch failed at ([^\\n]*)")); static QRegExp conflictedFilesRE(QLatin1String("Merge conflict in ([^\\n]*)")); @@ -711,11 +819,6 @@ public: m_files.append(conflictedFilesRE.cap(1)); } } -public slots: - void readStdOut(const QByteArray &data) - { - readStdOutString(QString::fromUtf8(data)); - } void readStdErr(const QString &data) { @@ -730,21 +833,39 @@ private: QStringList m_files; }; +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::IEditor *ed, Core::ICore::editorManager()->openedEditors()) - if (ed->document()->property(property).toString() == entry) - return ed; + foreach (Core::IDocument *document, Core::EditorManager::documentModel()->openedDocuments()) + if (document->property(property).toString() == entry) + return Core::EditorManager::documentModel()->editorsForDocument(document).first(); return 0; } // Return converted command output, remove '\r' read on Windows static inline QString commandOutputFromLocal8Bit(const QByteArray &a) { - QString output = QString::fromLocal8Bit(a); - output.remove(QLatin1Char('\r')); - return output; + return Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(a)); } // Return converted command output split into lines @@ -774,13 +895,24 @@ 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::IEditor *editor = Core::EditorManager::currentEditor()) - return QFileInfo(editor->document()->fileName()).path(); + 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"); +} + // ---------------- GitClient const char *GitClient::stashNamePrefix = "stash@{"; @@ -850,53 +982,60 @@ VcsBase::VcsBaseEditorWidget *GitClient::findExistingVCSEditor(const char *regis // Exists already Core::EditorManager::activateEditor(outputEditor); - outputEditor->createNew(m_msgWait); + outputEditor->document()->setContents(m_msgWait.toUtf8()); rc = VcsBase::VcsBaseEditorWidget::getVcsBaseEditor(outputEditor); return rc; } -DiffEditor::DiffEditor *GitClient::findExistingOrOpenNewDiffEditor(const char *registerDynamicProperty, - const QString &dynamicPropertyValue, const QString &titlePattern, const Core::Id editorId) const +DiffEditor::DiffEditor *GitClient::findExistingDiffEditor(const char *registerDynamicProperty, + const QString &dynamicPropertyValue) const { - Core::IEditor *outputEditor = locateEditor(registerDynamicProperty, dynamicPropertyValue); - if (outputEditor) { - // Exists already - Core::EditorManager::activateEditor(outputEditor); - outputEditor->createNew(m_msgWait); - } - - DiffEditor::DiffEditor *editor = qobject_cast<DiffEditor::DiffEditor *>(outputEditor); - if (!editor) { - QString title = titlePattern; - editor = qobject_cast<DiffEditor::DiffEditor *>( - Core::EditorManager::openEditorWithContents(editorId, &title, m_msgWait)); - editor->document()->setProperty(registerDynamicProperty, dynamicPropertyValue); - Core::EditorManager::activateEditor(editor); + DiffEditor::DiffEditor *diffEditor = qobject_cast<DiffEditor::DiffEditor *>( + locateEditor(registerDynamicProperty, dynamicPropertyValue)); + if (diffEditor) { + diffEditor->document()->setContents(m_msgWait.toUtf8()); + Core::EditorManager::activateEditor(diffEditor); } - return editor; + return diffEditor; } +DiffEditor::DiffEditor *GitClient::createDiffEditor(const char *registerDynamicProperty, + const QString &dynamicPropertyValue, + const QString &source, + const QString &titlePattern, + const Core::Id editorId) const +{ + QString title = titlePattern; + DiffEditor::DiffEditor *diffEditor = qobject_cast<DiffEditor::DiffEditor *>( + Core::EditorManager::openEditorWithContents(editorId, &title, m_msgWait.toUtf8())); + QTC_ASSERT(diffEditor, return 0); + diffEditor->document()->setProperty(registerDynamicProperty, dynamicPropertyValue); + diffEditor->editorWidget()->setSource(source); + + Core::EditorManager::activateEditor(diffEditor); + return diffEditor; +} /* Create an editor associated to VCS output of a source file/directory * (using the file's codec). Makes use of a dynamic property to find an * existing instance and to reuse it (in case, say, 'git diff foo' is * already open). */ -VcsBase::VcsBaseEditorWidget *GitClient::createVcsEditor(const Core::Id &id, - QString title, - // Source file or directory - const QString &source, - CodecType codecType, - // Dynamic property and value to identify that editor - const char *registerDynamicProperty, - const QString &dynamicPropertyValue, - QWidget *configWidget) const +VcsBase::VcsBaseEditorWidget *GitClient::createVcsEditor( + const Core::Id &id, + QString title, + const QString &source, // Source file or directory + CodecType codecType, + const char *registerDynamicProperty, // Dynamic property and value to identify that editor + const QString &dynamicPropertyValue, + VcsBase::VcsBaseEditorParameterWidget *configWidget) const { VcsBase::VcsBaseEditorWidget *rc = 0; QTC_CHECK(!findExistingVCSEditor(registerDynamicProperty, dynamicPropertyValue)); // Create new, set wait message, set up with source and codec - Core::IEditor *outputEditor = Core::EditorManager::openEditorWithContents(id, &title, m_msgWait); + Core::IEditor *outputEditor = Core::EditorManager::openEditorWithContents(id, &title, + m_msgWait.toUtf8()); outputEditor->document()->setProperty(registerDynamicProperty, dynamicPropertyValue); rc = VcsBase::VcsBaseEditorWidget::getVcsBaseEditor(outputEditor); connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)), @@ -922,21 +1061,28 @@ VcsBase::VcsBaseEditorWidget *GitClient::createVcsEditor(const Core::Id &id, } void GitClient::diff(const QString &workingDirectory, - const QStringList &diffArgs, const QStringList &unstagedFileNames, const QStringList &stagedFileNames) { const QString title = tr("Git Diff"); const int timeout = settings()->intValue(GitSettings::timeoutKey); - + Core::IEditor *newEditor = 0; if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - DiffEditor::DiffEditor *editor = findExistingOrOpenNewDiffEditor( - "originalFileName", - workingDirectory, - title, - DiffEditor::Constants::DIFF_EDITOR_ID); + const char *propertyName = "sideBySideOriginalFileName"; + DiffEditor::DiffEditor *diffEditor = findExistingDiffEditor(propertyName, workingDirectory); + if (!diffEditor) { + newEditor = diffEditor = createDiffEditor(propertyName, + workingDirectory, + workingDirectory, + title, + DiffEditor::Constants::DIFF_EDITOR_ID); + } - GitDiffHandler *handler = new GitDiffHandler(editor, gitBinaryPath(), workingDirectory, processEnvironment(), timeout); + GitDiffHandler *handler = new GitDiffHandler(diffEditor, + gitBinaryPath(), + workingDirectory, + processEnvironment(), + timeout); if (unstagedFileNames.empty() && stagedFileNames.empty()) { // local repository diff @@ -950,31 +1096,43 @@ void GitClient::diff(const QString &workingDirectory, } } else { const QString binary = settings()->stringValue(GitSettings::binaryPathKey); - const Core::Id editorId = Git::Constants::GIT_DIFF_EDITOR_ID; - - VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("originalFileName", workingDirectory); - if (!editor) { + const char *propertyName = "originalFileName"; + VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, workingDirectory); + if (!vcsEditor) { GitCommitDiffArgumentsWidget *argWidget = - new GitCommitDiffArgumentsWidget(this, workingDirectory, diffArgs, - unstagedFileNames, stagedFileNames); - - editor = createVcsEditor(editorId, title, - workingDirectory, CodecSource, "originalFileName", workingDirectory, argWidget); - connect(editor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), argWidget, SLOT(executeCommand())); + new GitCommitDiffArgumentsWidget(this, + workingDirectory, + unstagedFileNames, + stagedFileNames); + vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, + title, + workingDirectory, + CodecSource, + propertyName, + workingDirectory, + argWidget); + newEditor = vcsEditor->editor(); + connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)), + argWidget, SLOT(executeCommand())); + connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), + argWidget, SLOT(executeCommand())); } - GitCommitDiffArgumentsWidget *argWidget = qobject_cast<GitCommitDiffArgumentsWidget *>(editor->configurationWidget()); + GitCommitDiffArgumentsWidget *argWidget = qobject_cast<GitCommitDiffArgumentsWidget *>( + vcsEditor->configurationWidget()); + argWidget->setFileNames(unstagedFileNames, stagedFileNames); QStringList userDiffArgs = argWidget->arguments(); - editor->setDiffBaseDirectory(workingDirectory); + vcsEditor->setDiffBaseDirectory(workingDirectory); // Create a batch of 2 commands to be run after each other in case // we have a mixture of staged/unstaged files as is the case // when using the submit dialog. - VcsBase::Command *command = createCommand(workingDirectory, editor); + VcsBase::Command *command = createCommand(workingDirectory, vcsEditor); // Directory diff? QStringList cmdArgs; - cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption); + cmdArgs << QLatin1String("diff") + << QLatin1String(noColorOption); if (unstagedFileNames.empty() && stagedFileNames.empty()) { QStringList arguments(cmdArgs); @@ -985,65 +1143,99 @@ void GitClient::diff(const QString &workingDirectory, // Files diff. if (!unstagedFileNames.empty()) { QStringList arguments(cmdArgs); - arguments << userDiffArgs; - arguments << QLatin1String("--") << unstagedFileNames; + arguments << userDiffArgs + << QLatin1String("--") + << unstagedFileNames; outputWindow()->appendCommand(workingDirectory, binary, arguments); command->addJob(arguments, timeout); } if (!stagedFileNames.empty()) { QStringList arguments(cmdArgs); - arguments << userDiffArgs; - arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames; + arguments << userDiffArgs + << QLatin1String("--cached") + << QLatin1String("--") + << stagedFileNames; outputWindow()->appendCommand(workingDirectory, binary, arguments); command->addJob(arguments, timeout); } } command->execute(); } + if (newEditor) { + GitDiffSwitcher *switcher = new GitDiffSwitcher(newEditor, this); + switcher->setWorkingDirectory(workingDirectory); + if (unstagedFileNames.empty() && stagedFileNames.empty()) { + // local repository diff + switcher->setDiffType(GitDiffSwitcher::DiffRepository); + } else if (!stagedFileNames.empty()) { + // diff of selected files only with --cached option, used in commit editor + switcher->setDiffType(GitDiffSwitcher::DiffFileList); + switcher->setFileList(stagedFileNames, unstagedFileNames); + } else { + // current project diff + switcher->setDiffType(GitDiffSwitcher::DiffProjectList); + switcher->setProjectList(unstagedFileNames); + } + } } -void GitClient::diff(const QString &workingDirectory, - const QStringList &diffArgs, - const QString &fileName) +void GitClient::diff(const QString &workingDirectory, const QString &fileName) { const QString title = tr("Git Diff \"%1\"").arg(fileName); + const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileName); + Core::IEditor *newEditor = 0; if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileName); - DiffEditor::DiffEditor *editor = findExistingOrOpenNewDiffEditor( - "originalFileName", - sourceFile, - title, - DiffEditor::Constants::DIFF_EDITOR_ID); - - if (!fileName.isEmpty()) { - int timeout = settings()->intValue(GitSettings::timeoutKey); - GitDiffHandler *handler = new GitDiffHandler(editor, gitBinaryPath(), workingDirectory, processEnvironment(), timeout); - handler->diffFile(fileName); + const char *propertyName = "sideBySideOriginalFileName"; + DiffEditor::DiffEditor *diffEditor = findExistingDiffEditor(propertyName, sourceFile); + if (!diffEditor) { + newEditor = diffEditor = createDiffEditor(propertyName, + sourceFile, + sourceFile, + title, + DiffEditor::Constants::DIFF_EDITOR_ID); } + GitDiffHandler *handler = new GitDiffHandler(diffEditor, + gitBinaryPath(), + workingDirectory, + processEnvironment(), + settings()->intValue(GitSettings::timeoutKey)); + handler->diffFile(fileName); } else { - const Core::Id editorId = Git::Constants::GIT_DIFF_EDITOR_ID; - const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileName); - - VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("originalFileName", sourceFile); - if (!editor) { + const char *propertyName = "originalFileName"; + VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, sourceFile); + if (!vcsEditor) { GitFileDiffArgumentsWidget *argWidget = - new GitFileDiffArgumentsWidget(this, workingDirectory, diffArgs, fileName); - - editor = createVcsEditor(editorId, title, sourceFile, CodecSource, "originalFileName", sourceFile, argWidget); - connect(editor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), argWidget, SLOT(executeCommand())); + new GitFileDiffArgumentsWidget(this, workingDirectory, fileName); + + vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, + title, + sourceFile, + CodecSource, + propertyName, + sourceFile, + argWidget); + newEditor = vcsEditor->editor(); + connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)), + argWidget, SLOT(executeCommand())); + connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), + argWidget, SLOT(executeCommand())); } - editor->setDiffBaseDirectory(workingDirectory); - - GitFileDiffArgumentsWidget *argWidget = qobject_cast<GitFileDiffArgumentsWidget *>(editor->configurationWidget()); - QStringList userDiffArgs = argWidget->arguments(); + vcsEditor->setDiffBaseDirectory(workingDirectory); QStringList cmdArgs; - cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption) - << userDiffArgs; + cmdArgs << QLatin1String("diff") + << QLatin1String(noColorOption) + << vcsEditor->configurationWidget()->arguments(); if (!fileName.isEmpty()) cmdArgs << QLatin1String("--") << fileName; - executeGit(workingDirectory, cmdArgs, editor); + executeGit(workingDirectory, cmdArgs, vcsEditor); + } + if (newEditor) { + GitDiffSwitcher *switcher = new GitDiffSwitcher(newEditor, this); + switcher->setWorkingDirectory(workingDirectory); + switcher->setDiffType(GitDiffSwitcher::DiffFile); + switcher->setFileName(fileName); } } @@ -1052,35 +1244,58 @@ void GitClient::diffBranch(const QString &workingDirectory, const QString &branchName) { const QString title = tr("Git Diff Branch \"%1\"").arg(branchName); + Core::IEditor *newEditor = 0; if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - DiffEditor::DiffEditor *editor = findExistingOrOpenNewDiffEditor( - "BranchName", - branchName, - title, - DiffEditor::Constants::DIFF_EDITOR_ID); - - int timeout = settings()->intValue(GitSettings::timeoutKey); - GitDiffHandler *handler = new GitDiffHandler(editor, gitBinaryPath(), workingDirectory, processEnvironment(), timeout); + const char *propertyName = "sideBySideBranchName"; + DiffEditor::DiffEditor *diffEditor = findExistingDiffEditor(propertyName, branchName); + if (!diffEditor) { + newEditor = diffEditor = createDiffEditor(propertyName, + branchName, + workingDirectory, + title, + DiffEditor::Constants::DIFF_EDITOR_ID); + } + + GitDiffHandler *handler = new GitDiffHandler(diffEditor, + gitBinaryPath(), + workingDirectory, + processEnvironment(), + settings()->intValue(GitSettings::timeoutKey)); handler->diffBranch(branchName); } else { - const Core::Id editorId = Git::Constants::GIT_DIFF_EDITOR_ID; + const char *propertyName = "BranchName"; const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, QStringList()); - VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("BranchName", branchName); - if (!editor) - editor = createVcsEditor(editorId, title, sourceFile, CodecSource, "BranchName", branchName, - new GitBranchDiffArgumentsWidget(this, workingDirectory, - diffArgs, branchName)); - editor->setDiffBaseDirectory(workingDirectory); - - GitBranchDiffArgumentsWidget *argWidget = qobject_cast<GitBranchDiffArgumentsWidget *>(editor->configurationWidget()); - QStringList userDiffArgs = argWidget->arguments(); + VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, branchName); + if (!vcsEditor) { + vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, + title, + sourceFile, + CodecSource, + propertyName, + branchName, + new GitBranchDiffArgumentsWidget(this, + workingDirectory, + diffArgs, + branchName)); + newEditor = vcsEditor->editor(); + } + vcsEditor->setDiffBaseDirectory(workingDirectory); QStringList cmdArgs; - cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption) - << userDiffArgs << branchName; + cmdArgs << QLatin1String("diff") + << QLatin1String(noColorOption) + << vcsEditor->configurationWidget()->arguments() + << branchName; - executeGit(workingDirectory, cmdArgs, editor); + executeGit(workingDirectory, cmdArgs, vcsEditor); + } + if (newEditor) { + GitDiffSwitcher *switcher = new GitDiffSwitcher(newEditor, this); + switcher->setWorkingDirectory(workingDirectory); + switcher->setDiffType(GitDiffSwitcher::DiffBranch); + switcher->setBaseArguments(diffArgs); + switcher->setBranchName(branchName); } } @@ -1093,8 +1308,7 @@ void GitClient::merge(const QString &workingDirectory, const QStringList &unmerg void GitClient::status(const QString &workingDirectory) { - // @TODO: Use "--no-color" once it is supported - QStringList statusArgs(QLatin1String("status")); + QStringList statusArgs = statusArguments(); statusArgs << QLatin1String("-u"); VcsBase::VcsBaseOutputWindow *outwin = outputWindow(); outwin->setRepository(workingDirectory); @@ -1103,20 +1317,19 @@ void GitClient::status(const QString &workingDirectory) Qt::QueuedConnection); } -void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, +void GitClient::log(const QString &workingDirectory, const QString &fileName, bool enableAnnotationContextMenu, const QStringList &args) { - const QString msgArg = fileNames.empty() ? workingDirectory : - fileNames.join(QLatin1String(", ")); + const QString msgArg = fileName.isEmpty() ? workingDirectory : fileName; const QString title = tr("Git Log \"%1\"").arg(msgArg); const Core::Id editorId = Git::Constants::GIT_LOG_EDITOR_ID; - const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileNames); + const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileName); VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("logFileName", sourceFile); if (!editor) editor = createVcsEditor(editorId, title, sourceFile, CodecLogOutput, "logFileName", sourceFile, new GitLogArgumentsWidget(this, workingDirectory, enableAnnotationContextMenu, - args, fileNames)); + args, fileName)); editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu); editor->setDiffBaseDirectory(workingDirectory); @@ -1129,12 +1342,35 @@ void GitClient::log(const QString &workingDirectory, const QStringList &fileName arguments << QLatin1String("-n") << QString::number(logCount); GitLogArgumentsWidget *argWidget = qobject_cast<GitLogArgumentsWidget *>(editor->configurationWidget()); + argWidget->setBaseArguments(args); + argWidget->setFileName(fileName); QStringList userArgs = argWidget->arguments(); arguments.append(userArgs); - if (!fileNames.isEmpty()) - arguments << QLatin1String("--") << fileNames; + if (!fileName.isEmpty()) + arguments << QLatin1String("--follow") << QLatin1String("--") << fileName; + + executeGit(workingDirectory, arguments, editor); +} + +void GitClient::reflog(const QString &workingDirectory) +{ + const QString title = tr("Git Reflog \"%1\"").arg(workingDirectory); + const Core::Id editorId = Git::Constants::GIT_LOG_EDITOR_ID; + VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("reflogRepository", workingDirectory); + if (!editor) { + editor = createVcsEditor(editorId, title, workingDirectory, CodecLogOutput, + "reflogRepository", workingDirectory, 0); + } + + QStringList arguments; + arguments << QLatin1String("reflog") << QLatin1String(noColorOption) + << QLatin1String(decorateOption); + + int logCount = settings()->intValue(GitSettings::logCountKey); + if (logCount > 0) + arguments << QLatin1String("-n") << QString::number(logCount); executeGit(workingDirectory, arguments, editor); } @@ -1158,43 +1394,65 @@ void GitClient::show(const QString &source, const QString &id, const QStringList &args, const QString &name) { if (!canShow(id)) { - outputWindow()->append(msgCannotShow(id)); + outputWindow()->appendError(msgCannotShow(id)); return; } const QString title = tr("Git Show \"%1\"").arg(name.isEmpty() ? id : name); const QFileInfo sourceFi(source); - const QString workDir = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath(); + const QString workingDirectory = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath(); + Core::IEditor *newEditor = 0; if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - DiffEditor::DiffEditor *editor = findExistingOrOpenNewDiffEditor( - "show", - id, - title, - DiffEditor::Constants::DIFF_SHOW_EDITOR_ID); - - int timeout = settings()->intValue(GitSettings::timeoutKey); - GitDiffHandler *handler = new GitDiffHandler(editor, gitBinaryPath(), - findRepositoryForDirectory(workDir), - processEnvironment(), timeout); + const char *propertyName = "sideBySideShow"; + DiffEditor::DiffEditor *diffEditor = findExistingDiffEditor(propertyName, id); + if (!diffEditor) { + newEditor = diffEditor = createDiffEditor(propertyName, + id, + source, + title, + DiffEditor::Constants::DIFF_SHOW_EDITOR_ID); + } + + GitDiffHandler *handler = new GitDiffHandler(diffEditor, + gitBinaryPath(), + workingDirectory, + processEnvironment(), + settings()->intValue(GitSettings::timeoutKey)); handler->show(id); } else { + const char *propertyName = "show"; const Core::Id editorId = Git::Constants::GIT_DIFF_EDITOR_ID; - VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("show", id); - if (!editor) - editor = createVcsEditor(editorId, title, source, CodecSource, "show", id, - new GitShowArgumentsWidget(this, source, args, id)); - - GitShowArgumentsWidget *argWidget = qobject_cast<GitShowArgumentsWidget *>(editor->configurationWidget()); - QStringList userArgs = argWidget->arguments(); + VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, id); + if (!vcsEditor) { + vcsEditor = createVcsEditor(editorId, + title, + source, + CodecSource, + propertyName, + id, + new GitShowArgumentsWidget(this, + source, + args, + id)); + newEditor = vcsEditor->editor(); + } QStringList arguments; - arguments << QLatin1String("show") << QLatin1String(noColorOption); - arguments << QLatin1String(decorateOption); - arguments.append(userArgs); - arguments << id; + arguments << QLatin1String("show") + << QLatin1String(noColorOption) + << QLatin1String(decorateOption) + << vcsEditor->configurationWidget()->arguments() + << id; - editor->setDiffBaseDirectory(workDir); - executeGit(workDir, arguments, editor); + vcsEditor->setDiffBaseDirectory(workingDirectory); + executeGit(workingDirectory, arguments, vcsEditor); + } + if (newEditor) { + GitDiffSwitcher *switcher = new GitDiffSwitcher(newEditor, this); + switcher->setDiffType(GitDiffSwitcher::DiffShow); + switcher->setFileName(source); + switcher->setBaseArguments(args); + switcher->setId(id); } } @@ -1214,18 +1472,6 @@ void GitClient::slotBlameRevisionRequested(const QString &source, QString change blame(fi.absolutePath(), QStringList(), fi.fileName(), change, lineNumber); } -void GitClient::appendOutputData(const QByteArray &data) const -{ - const QTextCodec *codec = getSourceCodec(currentDocumentPath()); - outputWindow()->appendData(codec->toUnicode(data).toLocal8Bit()); -} - -void GitClient::appendOutputDataSilently(const QByteArray &data) const -{ - const QTextCodec *codec = getSourceCodec(currentDocumentPath()); - outputWindow()->appendDataSilently(codec->toUnicode(data).toLocal8Bit()); -} - QTextCodec *GitClient::getSourceCodec(const QString &file) const { if (QFileInfo(file).isFile()) @@ -1256,12 +1502,9 @@ void GitClient::blame(const QString &workingDirectory, argWidget->setEditor(editor); } - GitBlameArgumentsWidget *argWidget = qobject_cast<GitBlameArgumentsWidget *>(editor->configurationWidget()); - QStringList userBlameArgs = argWidget->arguments(); - QStringList arguments(QLatin1String("blame")); arguments << QLatin1String("--root"); - arguments.append(userBlameArgs); + arguments.append(editor->configurationWidget()->arguments()); arguments << QLatin1String("--") << fileName; if (!revision.isEmpty()) arguments << revision; @@ -1446,11 +1689,11 @@ bool GitClient::synchronousInit(const QString &workingDirectory) const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText); // '[Re]Initialized...' outputWindow()->append(commandOutputFromLocal8Bit(outputText)); - if (!rc) + if (!rc) { outputWindow()->appendError(commandOutputFromLocal8Bit(errorText)); - else { + } else { // TODO: Turn this into a VcsBaseClient and use resetCachedVcsInfo(...) - Core::ICore::vcsManager()->resetVersionControlForDirectory(workingDirectory); + Core::VcsManager::resetVersionControlForDirectory(workingDirectory); } return rc; } @@ -1540,9 +1783,9 @@ bool GitClient::synchronousRevListCmd(const QString &workingDirectory, const QSt if (errorMessage) *errorMessage = commandOutputFromLocal8Bit(errorText); else - outputWindow()->append(tr("Cannot execute \"git %1\" in \"%2\": %3").arg( - args.join(QLatin1String(" ")), workingDirectory, - commandOutputFromLocal8Bit(errorText))); + outputWindow()->appendError(tr("Cannot execute \"git %1\" in \"%2\": %3").arg( + args.join(QLatin1String(" ")), workingDirectory, + commandOutputFromLocal8Bit(errorText))); return false; } *output = commandOutputFromLocal8Bit(outputTextData); @@ -1620,11 +1863,6 @@ QString GitClient::synchronousCurrentLocalBranch(const QString &workingDirectory return QString(); } -static inline QString msgCannotDetermineBranch(const QString &workingDirectory, const QString &why) -{ - return GitClient::tr("Cannot retrieve branch of \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), why); -} - static inline QString msgCannotRun(const QString &command, const QString &workingDirectory, const QString &why) { return GitClient::tr("Cannot run \"%1\" in \"%2\": %3") @@ -1646,7 +1884,7 @@ bool GitClient::synchronousHeadRefs(const QString &workingDirectory, QStringList if (errorMessage) *errorMessage = message; else - outputWindow()->append(message); + outputWindow()->appendError(message); return false; } @@ -1690,20 +1928,6 @@ QString GitClient::synchronousTopic(const QString &workingDirectory) if (!branch.isEmpty()) return data.topic = branch; - switch (checkCommandInProgressInGitDir(gitDir)) { - case Rebase: - case RebaseMerge: - return data.topic = tr("REBASING"); - case Revert: - return data.topic = tr("REVERTING"); - case CherryPick: - return data.topic = tr("CHERRY-PICKING"); - case Merge: - return data.topic = tr("MERGING"); - default: - break; - } - // Detached HEAD, try a tag or remote branch QStringList references; if (!synchronousHeadRefs(workingDirectory, &references)) @@ -1757,17 +1981,19 @@ QString GitClient::synchronousTopRevision(const QString &workingDirectory, QStri } void GitClient::synchronousTagsForCommit(const QString &workingDirectory, const QString &revision, - QByteArray &precedes, QByteArray &follows) + QString &precedes, QString &follows) { + QByteArray pr; QStringList arguments; arguments << QLatin1String("describe") << QLatin1String("--contains") << revision; - fullySynchronousGit(workingDirectory, arguments, &precedes, 0, + fullySynchronousGit(workingDirectory, arguments, &pr, 0, VcsBasePlugin::SuppressCommandLogging); - int tilde = precedes.indexOf('~'); + int tilde = pr.indexOf('~'); if (tilde != -1) - precedes.truncate(tilde); + pr.truncate(tilde); else - precedes = precedes.trimmed(); + pr = pr.trimmed(); + precedes = QString::fromLocal8Bit(pr); QStringList parents; QString errorMessage; @@ -1782,12 +2008,42 @@ void GitClient::synchronousTagsForCommit(const QString &workingDirectory, const pf.truncate(pf.lastIndexOf('\n')); if (!pf.isEmpty()) { if (!follows.isEmpty()) - follows += ", "; - follows += pf; + follows += QLatin1String(", "); + follows += QString::fromLocal8Bit(pf); } } } +QStringList GitClient::synchronousBranchesForCommit(const QString &workingDirectory, const QString &revision) +{ + QByteArray outputData; + QString output; + QStringList arguments; + arguments << QLatin1String("branch") << QLatin1String(noColorOption) + << QLatin1String("-a") << QLatin1String("--contains") << revision; + fullySynchronousGit(workingDirectory, arguments, &outputData, 0, + VcsBasePlugin::SuppressCommandLogging); + output = commandOutputFromLocal8Bit(outputData); + QStringList res; + foreach (const QString &branch, output.split(QLatin1Char('\n'))) { + const QString b = branch.mid(2).trimmed(); + if (!b.isEmpty()) + res << b; + } + return res; +} + +bool GitClient::isRemoteCommit(const QString &workingDirectory, const QString &commit) +{ + QStringList arguments; + QByteArray outputText; + arguments << QLatin1String("branch") << QLatin1String("-r") + << QLatin1String("--contains") << commit; + fullySynchronousGit(workingDirectory, arguments, &outputText, 0, + VcsBasePlugin::SuppressCommandLogging); + return !outputText.isEmpty(); +} + // Format an entry in a one-liner for selection list using git log. QString GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision, const QString &format) @@ -1862,10 +2118,10 @@ QString GitClient::synchronousStash(const QString &workingDirectory, const QStri if (unchanged) *unchanged = true; if (!(flags & StashIgnoreUnchanged)) - outputWindow()->append(msgNoChangedFiles()); + outputWindow()->appendWarning(msgNoChangedFiles()); break; case StatusFailed: - outputWindow()->append(errorMessage); + outputWindow()->appendError(errorMessage); break; } if (!success) @@ -1923,7 +2179,7 @@ bool GitClient::stashNameFromMessage(const QString &workingDirectory, if (errorMessage) *errorMessage = msg; else - outputWindow()->append(msg); + outputWindow()->appendError(msg); return false; } @@ -1935,8 +2191,23 @@ bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringLis QByteArray errorText; const bool rc = fullySynchronousGit(workingDirectory, branchArgs, &outputText, &errorText); *output = commandOutputFromLocal8Bit(outputText); + if (!rc && errorMessage) { + *errorMessage = msgCannotRun(QLatin1String("git branch"), workingDirectory, + commandOutputFromLocal8Bit(errorText)); + } + return rc; +} + +bool GitClient::synchronousTagCmd(const QString &workingDirectory, QStringList tagArgs, QString *output, QString *errorMessage) +{ + tagArgs.push_front(QLatin1String("tag")); + QByteArray outputText; + QByteArray errorText; + const bool rc = fullySynchronousGit(workingDirectory, tagArgs, &outputText, &errorText); + *output = commandOutputFromLocal8Bit(outputText); if (!rc) { - *errorMessage = msgCannotRun(QLatin1String("git branch"), workingDirectory, commandOutputFromLocal8Bit(errorText)); + *errorMessage = msgCannotRun(QLatin1String("git tag"), workingDirectory, + commandOutputFromLocal8Bit(errorText)); return false; } return true; @@ -1951,8 +2222,12 @@ bool GitClient::synchronousForEachRefCmd(const QString &workingDirectory, QStrin const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText); *output = commandOutputFromLocal8Bit(outputText); if (!rc) { - *errorMessage = msgCannotRun(QLatin1String("git for-each-ref"), workingDirectory, + QString error = msgCannotRun(QLatin1String("git for-each-ref"), workingDirectory, commandOutputFromLocal8Bit(errorText)); + if (errorMessage) + *errorMessage = error; + else + outputWindow()->appendError(error); return false; } @@ -1985,7 +2260,7 @@ QMap<QString,QString> GitClient::synchronousRemotesList(const QString &workingDi if (errorMessage) *errorMessage = error; else - outputWindow()->append(error); + outputWindow()->appendError(error); return result; } QStringList remotes = output.split(QLatin1String("\n")); @@ -1994,12 +2269,11 @@ QMap<QString,QString> GitClient::synchronousRemotesList(const QString &workingDi if (!remote.endsWith(QLatin1String(" (push)"))) continue; - QStringList tokens = remote.split(QRegExp(QLatin1String("\\s")), - QString::SkipEmptyParts); - if (tokens.count() != 3) + int tabIndex = remote.indexOf(QLatin1Char('\t')); + if (tabIndex == -1) continue; - - result.insert(tokens.at(0), tokens.at(1)); + QString url = remote.mid(tabIndex + 1, remote.length() - tabIndex - 8); + result.insert(remote.left(tabIndex), url); } return result; } @@ -2021,7 +2295,7 @@ QStringList GitClient::synchronousSubmoduleStatus(const QString &workingDirector if (errorMessage) *errorMessage = error; else - outputWindow()->append(error); + outputWindow()->appendError(error); return QStringList(); } @@ -2143,10 +2417,11 @@ bool GitClient::synchronousCleanList(const QString &workingDirectory, QStringLis } bool GitClient::synchronousApplyPatch(const QString &workingDirectory, - const QString &file, QString *errorMessage) + const QString &file, QString *errorMessage, + const QStringList &arguments) { QStringList args; - args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << file; + args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << arguments << file; QByteArray outputText; QByteArray errorText; const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText); @@ -2167,20 +2442,22 @@ VcsBase::Command *GitClient::createCommand(const QString &workingDirectory, int editorLineNumber) { VcsBase::Command *command = new VcsBase::Command(gitBinaryPath(), workingDirectory, processEnvironment()); + command->setCodec(getSourceCodec(currentDocumentPath())); command->setCookie(QVariant(editorLineNumber)); - if (editor) - connect(command, SIGNAL(finished(bool,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,QVariant))); + if (editor) { + editor->setCommand(command); + connect(command, SIGNAL(finished(bool,int,QVariant)), + editor, SLOT(commandFinishedGotoLine(bool,int,QVariant))); + } if (useOutputToWindow) { + command->addFlags(VcsBasePlugin::ShowStdOutInLogWindow); + command->addFlags(VcsBasePlugin::ShowSuccessMessage); if (editor) // assume that the commands output is the important thing - connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(appendOutputDataSilently(QByteArray))); - else - connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(appendOutputData(QByteArray))); - } else { - if (editor) - connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray))); + command->addFlags(VcsBasePlugin::SilentOutput); + } else if (editor) { + connect(command, SIGNAL(output(QString)), editor, SLOT(setPlainTextFiltered(QString))); } - connect(command, SIGNAL(errorText(QString)), outputWindow(), SLOT(appendError(QString))); return command; } @@ -2195,9 +2472,8 @@ VcsBase::Command *GitClient::executeGit(const QString &workingDirectory, outputWindow()->appendCommand(workingDirectory, settings()->stringValue(GitSettings::binaryPathKey), arguments); VcsBase::Command *command = createCommand(workingDirectory, editor, useOutputToWindow, editorLineNumber); command->addJob(arguments, settings()->intValue(GitSettings::timeoutKey)); - command->setTerminationReportMode(VcsBase::Command::NoReport); - command->setUnixTerminalDisabled(false); - command->setExpectChanges(expectChanges); + if (expectChanges) + command->addFlags(VcsBasePlugin::ExpectRepoChanges); command->execute(); return command; } @@ -2221,12 +2497,12 @@ QProcessEnvironment GitClient::processEnvironment() const return environment; } -bool GitClient::beginStashScope(const QString &workingDirectory, const QString &keyword, StashFlag flag) +bool GitClient::beginStashScope(const QString &workingDirectory, const QString &command, StashFlag flag) { const QString repoDirectory = findRepositoryForDirectory(workingDirectory); QTC_ASSERT(!repoDirectory.isEmpty(), return false); StashInfo &stashInfo = m_stashInfo[repoDirectory]; - return stashInfo.init(repoDirectory, keyword, flag); + return stashInfo.init(repoDirectory, command, flag); } GitClient::StashInfo &GitClient::stashInfo(const QString &workingDirectory) @@ -2258,12 +2534,11 @@ bool GitClient::isValidRevision(const QString &revision) const Utils::SynchronousProcessResponse GitClient::synchronousGit(const QString &workingDirectory, const QStringList &gitArguments, unsigned flags, - QTextCodec *stdOutCodec) + QTextCodec *outputCodec) { return VcsBasePlugin::runVcs(workingDirectory, gitBinaryPath(), gitArguments, settings()->intValue(GitSettings::timeoutKey) * 1000, - processEnvironment(), - flags, stdOutCodec); + flags, outputCodec, processEnvironment()); } bool GitClient::fullySynchronousGit(const QString &workingDirectory, @@ -2272,10 +2547,11 @@ bool GitClient::fullySynchronousGit(const QString &workingDirectory, QByteArray* errorText, unsigned flags) const { - return VcsBasePlugin::runFullySynchronous(workingDirectory, gitBinaryPath(), gitArguments, - processEnvironment(), outputText, errorText, - settings()->intValue(GitSettings::timeoutKey) * 1000, - flags); + VcsBase::Command command(gitBinaryPath(), workingDirectory, processEnvironment()); + command.addFlags(flags); + return command.runFullySynchronous(gitArguments, + settings()->intValue(GitSettings::timeoutKey) * 1000, + outputText, errorText); } void GitClient::updateSubmodulesIfNeeded(const QString &workingDirectory, bool prompt) @@ -2342,21 +2618,6 @@ void GitClient::fetchFinished(const QVariant &cookie) GitPlugin::instance()->updateBranches(cookie.toString()); } -// Trim a git status file spec: "modified: foo .cpp" -> "modified: foo .cpp" -static inline QString trimFileSpecification(QString fileSpec) -{ - const int colonIndex = fileSpec.indexOf(QLatin1Char(':')); - if (colonIndex != -1) { - // Collapse the sequence of spaces - const int filePos = colonIndex + 2; - int nonBlankPos = filePos; - for ( ; fileSpec.at(nonBlankPos).isSpace(); nonBlankPos++) ; - if (nonBlankPos > filePos) - fileSpec.remove(filePos, nonBlankPos - filePos); - } - return fileSpec; -} - GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory, StatusMode mode, QString *output, QString *errorMessage) { @@ -2364,7 +2625,7 @@ GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory, St QByteArray outputText; QByteArray errorText; - QStringList statusArgs(QLatin1String("status")); + QStringList statusArgs = statusArguments(); if (mode & NoUntracked) statusArgs << QLatin1String("--untracked-files=no"); else @@ -2397,8 +2658,27 @@ GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory, St return StatusUnchanged; } -GitClient::CommandInProgress GitClient::checkCommandInProgressInGitDir(const QString &gitDir) +QString GitClient::commandInProgressDescription(const QString &workingDirectory) +{ + switch (checkCommandInProgress(workingDirectory)) { + case NoCommand: + break; + case Rebase: + case RebaseMerge: + return tr("REBASING"); + case Revert: + return tr("REVERTING"); + case CherryPick: + return tr("CHERRY-PICKING"); + case Merge: + return tr("MERGING"); + } + return QString(); +} + +GitClient::CommandInProgress GitClient::checkCommandInProgress(const QString &workingDirectory) { + const QString gitDir = findGitDirForRepository(workingDirectory); if (QFile::exists(gitDir + QLatin1String("/MERGE_HEAD"))) return Merge; else if (QFile::exists(gitDir + QLatin1String("/rebase-apply/rebasing"))) @@ -2413,15 +2693,15 @@ GitClient::CommandInProgress GitClient::checkCommandInProgressInGitDir(const QSt return NoCommand; } -GitClient::CommandInProgress GitClient::checkCommandInProgress(const QString &workingDirectory) -{ - return checkCommandInProgressInGitDir(findGitDirForRepository(workingDirectory)); -} - void GitClient::continueCommandIfNeeded(const QString &workingDirectory) { CommandInProgress command = checkCommandInProgress(workingDirectory); switch (command) { + case Merge: + continuePreviousGitCommand(workingDirectory, tr("Continue Merge"), + tr("Merge is in progress. What do you want to do?"), + tr("Continue"), QLatin1String("merge")); + break; case Rebase: case RebaseMerge: continuePreviousGitCommand(workingDirectory, tr("Continue Rebase"), @@ -2479,6 +2759,37 @@ void GitClient::continuePreviousGitCommand(const QString &workingDirectory, } } +QString GitClient::extendedShowDescription(const QString &workingDirectory, const QString &text) +{ + if (!text.startsWith(QLatin1String("commit "))) + return text; + QString modText = text; + QString precedes, follows; + int lastHeaderLine = modText.indexOf(QLatin1String("\n\n")) + 1; + const QString commit = modText.mid(7, 8); + synchronousTagsForCommit(workingDirectory, commit, precedes, follows); + if (!precedes.isEmpty()) + modText.insert(lastHeaderLine, QLatin1String("Precedes: ") + precedes + QLatin1Char('\n')); + if (!follows.isEmpty()) + modText.insert(lastHeaderLine, QLatin1String("Follows: ") + follows + QLatin1Char('\n')); + QString moreBranches; + QStringList branches = synchronousBranchesForCommit(workingDirectory, commit); + const int branchCount = branches.count(); + // If there are more than 20 branches, list first 10 followed by a hint + if (branchCount > 20) { + const int leave = 10; + //: Displayed after the untranslated message "Branches: branch1, branch2 'and %n more'" in git show. + moreBranches = tr(" and %n more", 0, branchCount - leave); + branches.erase(branches.begin() + leave, branches.end()); + } + if (!branches.isEmpty()) { + modText.insert(lastHeaderLine, QLatin1String("Branches: ") + + branches.join(QLatin1String(", ")) + moreBranches + + QLatin1Char('\n')); + } + return modText; +} + // Quietly retrieve branch list of remote repository URL // // The branch HEAD is pointing to is always returned first. @@ -2578,15 +2889,27 @@ bool GitClient::tryLauchingGitK(const QProcessEnvironment &env, success = QProcess::startDetached(binary, arguments, workingDirectory); } if (!success) { - const QString error = tr("Cannot launch \"%1\".").arg(binary); if (silent) - outwin->appendSilently(error); + outwin->appendSilently(msgCannotLaunch(binary)); else - outwin->appendError(error); + outwin->appendError(msgCannotLaunch(binary)); } return success; } +bool GitClient::launchGitGui(const QString &workingDirectory) { + bool success; + QString gitBinary = gitBinaryPath(&success); + if (success) + success = QProcess::startDetached(gitBinary, QStringList(QLatin1String("gui")), + workingDirectory); + + if (!success) + outputWindow()->appendError(msgCannotLaunch(QLatin1String("git gui"))); + + return success; +} + QString GitClient::gitBinaryPath(bool *ok, QString *errorMessage) const { return settings()->gitBinaryPath(ok, errorMessage); @@ -2721,6 +3044,8 @@ bool GitClient::getCommitData(const QString &workingDirectory, case FixupCommit: break; } + + commitData.panelData.hasRemotes = !synchronousRemotesList(repoDirectory).isEmpty(); return true; } @@ -2819,7 +3144,7 @@ bool GitClient::addAndCommit(const QString &repositoryDirectory, const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText); const QString stdErr = commandOutputFromLocal8Bit(errorText); if (rc) { - outputWindow()->append(msgCommitted(amendSHA1, commitCount)); + outputWindow()->appendMessage(msgCommitted(amendSHA1, commitCount)); outputWindow()->appendError(stdErr); } else { outputWindow()->appendError(tr("Cannot commit %n file(s): %1\n", 0, commitCount).arg(stdErr)); @@ -2928,11 +3253,11 @@ void GitClient::revert(const QStringList &files, bool revertStaging) break; case RevertUnchanged: { const QString msg = (isDirectory || files.size() > 1) ? msgNoChangedFiles() : tr("The file is not modified."); - outputWindow()->append(msg); + outputWindow()->appendWarning(msg); } break; case RevertFailed: - outputWindow()->append(errorMessage); + outputWindow()->appendError(errorMessage); break; } } @@ -2959,7 +3284,7 @@ bool GitClient::executeAndHandleConflicts(const QString &workingDirectory, // Notify about changed files or abort the rebase. const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished; if (!ok) { - conflictHandler.readStdOutString(resp.stdOut); + conflictHandler.readStdOut(resp.stdOut); conflictHandler.readStdErr(resp.stdErr); } return ok; @@ -3021,13 +3346,36 @@ QString GitClient::synchronousTrackingBranch(const QString &workingDirectory, co return remote + QLatin1Char('/') + rBranch; } +bool GitClient::synchronousSetTrackingBranch(const QString &workingDirectory, + const QString &branch, const QString &tracking) +{ + QByteArray outputText; + QByteArray errorText; + QStringList arguments; + arguments << QLatin1String("branch"); + if (gitVersion() >= 0x010800) + arguments << (QLatin1String("--set-upstream-to=") + tracking) << branch; + else + arguments << QLatin1String("--set-upstream") << branch << tracking; + const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText); + if (!rc) { + const QString errorMessage = tr("Cannot set tracking branch: %1") + .arg(commandOutputFromLocal8Bit(errorText)); + outputWindow()->appendError(errorMessage); + } + return rc; +} + void GitClient::handleMergeConflicts(const QString &workingDir, const QString &commit, const QStringList &files, const QString &abortCommand) { - Q_UNUSED(files); - - QString message = commit.isEmpty() ? tr("Conflicts detected") - : tr("Conflicts detected with commit %1").arg(commit); + QString message; + if (!commit.isEmpty()) + message = tr("Conflicts detected with commit %1.").arg(commit); + else if (!files.isEmpty()) + message = tr("Conflicts detected with files:\n%1").arg(files.join(QLatin1String("\n"))); + else + message = tr("Conflicts detected."); QMessageBox mergeOrAbort(QMessageBox::Question, tr("Conflicts Detected"), message, QMessageBox::NoButton, Core::ICore::mainWindow()); QPushButton *mergeToolButton = mergeOrAbort.addButton(tr("Run &Merge Tool"), @@ -3125,6 +3473,7 @@ void GitClient::rebase(const QString &workingDirectory, const QString &baseBranc arguments); VcsBase::Command *command = createCommand(workingDirectory, 0, true); new ConflictHandler(command, workingDirectory, gitCommand); + command->setProgressParser(new ProgressParser); command->addJob(arguments, -1); command->execute(); } @@ -3132,7 +3481,11 @@ void GitClient::rebase(const QString &workingDirectory, const QString &baseBranc bool GitClient::synchronousRevert(const QString &workingDirectory, const QString &commit) { QStringList arguments; - QString command = QLatin1String("revert"); + const QString command = QLatin1String("revert"); + // Do not stash if --continue or --abort is given as the commit + if (!commit.startsWith(QLatin1Char('-')) && !beginStashScope(workingDirectory, command)) + return false; + arguments << command << QLatin1String("--no-edit") << commit; return executeAndHandleConflicts(workingDirectory, arguments, command); @@ -3140,9 +3493,16 @@ bool GitClient::synchronousRevert(const QString &workingDirectory, const QString bool GitClient::synchronousCherryPick(const QString &workingDirectory, const QString &commit) { - QStringList arguments; - QString command = QLatin1String("cherry-pick"); - arguments << command << commit; + const QString command = QLatin1String("cherry-pick"); + // "commit" might be --continue or --abort + const bool isRealCommit = !commit.startsWith(QLatin1Char('-')); + if (isRealCommit && !beginStashScope(workingDirectory, command)) + return false; + + QStringList arguments(command); + if (isRealCommit && isRemoteCommit(workingDirectory, commit)) + arguments << QLatin1String("-x"); + arguments << commit; return executeAndHandleConflicts(workingDirectory, arguments, command); } @@ -3159,6 +3519,7 @@ void GitClient::interactiveRebase(const QString &workingDirectory, const QString m_disableEditor = true; VcsBase::Command *command = createCommand(workingDirectory, 0, true); new ConflictHandler(command, workingDirectory, QLatin1String("rebase")); + command->setProgressParser(new ProgressParser); command->addJob(arguments, -1); command->execute(); command->setCookie(workingDirectory); @@ -3217,7 +3578,7 @@ bool GitClient::synchronousStashRestore(const QString &workingDirectory, if (errorMessage) *errorMessage = msg; else - outputWindow()->append(msg); + outputWindow()->appendError(msg); return false; } QString output = commandOutputFromLocal8Bit(outputText); @@ -3249,7 +3610,7 @@ bool GitClient::synchronousStashRemove(const QString &workingDirectory, if (errorMessage) *errorMessage = msg; else - outputWindow()->append(msg); + outputWindow()->appendError(msg); return false; } QString output = commandOutputFromLocal8Bit(outputText); @@ -3289,7 +3650,7 @@ bool GitClient::synchronousStashList(const QString &workingDirectory, if (errorMessage) *errorMessage = msg; else - outputWindow()->append(msg); + outputWindow()->appendError(msg); return false; } Stash stash; @@ -3310,7 +3671,7 @@ QString GitClient::readConfig(const QString &workingDirectory, const QStringList VcsBasePlugin::SuppressCommandLogging)) return QString(); if (Utils::HostOsInfo::isWindowsHost()) - return QString::fromUtf8(outputText).remove(QLatin1Char('\r')); + return Utils::SynchronousProcess::normalizeNewlines(QString::fromUtf8(outputText)); return commandOutputFromLocal8Bit(outputText); } @@ -3365,7 +3726,7 @@ bool GitClient::cloneRepository(const QString &directory,const QByteArray &url) const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory.path(), arguments, flags); // TODO: Turn this into a VcsBaseClient and use resetCachedVcsInfo(...) - Core::ICore::vcsManager()->resetVersionControlForDirectory(workingDirectory.absolutePath()); + Core::VcsManager::resetVersionControlForDirectory(workingDirectory.absolutePath()); return (resp.result == Utils::SynchronousProcessResponse::Finished); } } @@ -3419,7 +3780,7 @@ unsigned GitClient::synchronousGitVersion(QString *errorMessage) const if (errorMessage) *errorMessage = msg; else - outputWindow()->append(msg); + outputWindow()->appendError(msg); return 0; } // cut 'git version 1.6.5.1.sha' @@ -3438,7 +3799,7 @@ GitClient::StashInfo::StashInfo() : { } -bool GitClient::StashInfo::init(const QString &workingDirectory, const QString &keyword, +bool GitClient::StashInfo::init(const QString &workingDirectory, const QString &command, StashFlag flag) { m_workingDir = workingDirectory; @@ -3449,9 +3810,9 @@ bool GitClient::StashInfo::init(const QString &workingDirectory, const QString & &statusOutput, &errorMessage)) { case GitClient::StatusChanged: if (m_flags & NoPrompt) - executeStash(keyword, &errorMessage); + executeStash(command, &errorMessage); else - stashPrompt(keyword, statusOutput, &errorMessage); + stashPrompt(command, statusOutput, &errorMessage); break; case GitClient::StatusUnchanged: m_stashResult = StashUnchanged; @@ -3466,7 +3827,7 @@ bool GitClient::StashInfo::init(const QString &workingDirectory, const QString & return !stashingFailed(); } -void GitClient::StashInfo::stashPrompt(const QString &keyword, const QString &statusOutput, +void GitClient::StashInfo::stashPrompt(const QString &command, const QString &statusOutput, QString *errorMessage) { QMessageBox msgBox(QMessageBox::Question, tr("Uncommitted Changes Found"), @@ -3476,40 +3837,45 @@ void GitClient::StashInfo::stashPrompt(const QString &keyword, const QString &st msgBox.setDetailedText(statusOutput); + QPushButton *stashAndPopButton = msgBox.addButton(tr("Stash && Pop"), QMessageBox::AcceptRole); + stashAndPopButton->setToolTip(tr("Stash local changes and pop when %1 finishes.").arg(command)); + QPushButton *stashButton = msgBox.addButton(tr("Stash"), QMessageBox::AcceptRole); - stashButton->setToolTip(tr("Stash local changes and continue.")); + stashButton->setToolTip(tr("Stash local changes and execute %1.").arg(command)); QPushButton *discardButton = msgBox.addButton(tr("Discard"), QMessageBox::AcceptRole); - discardButton->setToolTip(tr("Discard (reset) local changes and continue.")); + discardButton->setToolTip(tr("Discard (reset) local changes and execute %1.").arg(command)); QPushButton *ignoreButton = 0; if (m_flags & AllowUnstashed) { ignoreButton = msgBox.addButton(QMessageBox::Ignore); - ignoreButton->setToolTip(tr("Continue with local changes in working directory.")); + ignoreButton->setToolTip(tr("Execute %1 with local changes in working directory.") + .arg(command)); } QPushButton *cancelButton = msgBox.addButton(QMessageBox::Cancel); - cancelButton->setToolTip(tr("Cancel current command.")); + cancelButton->setToolTip(tr("Cancel %1.").arg(command)); msgBox.exec(); if (msgBox.clickedButton() == discardButton) { - if (!m_client->synchronousReset(m_workingDir, QStringList(), errorMessage)) - m_stashResult = StashFailed; - else - m_stashResult = StashUnchanged; + m_stashResult = m_client->synchronousReset(m_workingDir, QStringList(), errorMessage) ? + StashUnchanged : StashFailed; } else if (msgBox.clickedButton() == ignoreButton) { // At your own risk, so. m_stashResult = NotStashed; } else if (msgBox.clickedButton() == cancelButton) { m_stashResult = StashCanceled; } else if (msgBox.clickedButton() == stashButton) { - executeStash(keyword, errorMessage); + m_stashResult = m_client->executeSynchronousStash(m_workingDir, + creatorStashMessage(command), errorMessage) ? StashUnchanged : StashFailed; + } else if (msgBox.clickedButton() == stashAndPopButton) { + executeStash(command, errorMessage); } } -void GitClient::StashInfo::executeStash(const QString &keyword, QString *errorMessage) +void GitClient::StashInfo::executeStash(const QString &command, QString *errorMessage) { - m_message = creatorStashMessage(keyword); + m_message = creatorStashMessage(command); if (!m_client->executeSynchronousStash(m_workingDir, m_message, errorMessage)) m_stashResult = StashFailed; else |