// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "changeselectiondialog.h" #include "logchangedialog.h" #include "gitclient.h" #include "gittr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; using namespace VcsBase; namespace Git::Internal { ChangeSelectionDialog::ChangeSelectionDialog(const FilePath &workingDirectory, Id id, QWidget *parent) : QDialog(parent) { m_gitExecutable = gitClient().vcsBinary(workingDirectory); m_gitEnvironment = gitClient().processEnvironment(workingDirectory); resize(550, 350); setWindowTitle(Tr::tr("Select a Git Commit")); setObjectName("Git.ChangeSelectionDialog"); m_workingDirectoryChooser = new PathChooser(this); m_changeNumberEdit = new CompletingLineEdit(this); m_changeNumberEdit->setObjectName("changeNumberEdit"); m_changeNumberEdit->setText(Tr::tr("HEAD")); m_changeNumberEdit->setFocus(); m_changeNumberEdit->selectAll(); m_detailsText = new QPlainTextEdit(this); m_detailsText->setObjectName("detailsText"); m_detailsText->setUndoRedoEnabled(false); m_detailsText->setLineWrapMode(QPlainTextEdit::NoWrap); m_detailsText->setReadOnly(true); auto selectFromHistoryButton = new QPushButton(Tr::tr("Browse &History...")); auto closeButton = new QPushButton(Tr::tr("&Close")); auto archiveButton = new QPushButton(Tr::tr("&Archive...")); m_checkoutButton = new QPushButton(Tr::tr("Check&out")); m_revertButton = new QPushButton(Tr::tr("&Revert")); m_cherryPickButton = new QPushButton(Tr::tr("Cherry &Pick")); m_showButton = new QPushButton(Tr::tr("&Show")); m_showButton->setObjectName("showButton"); m_workingDirectoryChooser->setExpectedKind(PathChooser::ExistingDirectory); m_workingDirectoryChooser->setPromptDialogTitle(Tr::tr("Select Git Directory")); m_workingDirectoryChooser->setFilePath(workingDirectory); using namespace Layouting; Column { Grid { Tr::tr("Working directory:"), m_workingDirectoryChooser, br, Tr::tr("Change:"), m_changeNumberEdit, selectFromHistoryButton, }, m_detailsText, Row { closeButton, st, archiveButton, m_checkoutButton, m_revertButton, m_cherryPickButton, m_showButton } }.attachTo(this); connect(m_changeNumberEdit, &CompletingLineEdit::textChanged, this, &ChangeSelectionDialog::changeTextChanged); connect(m_workingDirectoryChooser, &PathChooser::textChanged, this, &ChangeSelectionDialog::recalculateDetails); connect(m_workingDirectoryChooser, &PathChooser::textChanged, this, &ChangeSelectionDialog::recalculateCompletion); connect(selectFromHistoryButton, &QPushButton::clicked, this, &ChangeSelectionDialog::selectCommitFromRecentHistory); connect(m_showButton, &QPushButton::clicked, this, std::bind(&ChangeSelectionDialog::acceptCommand, this, Show)); connect(m_cherryPickButton, &QPushButton::clicked, this, std::bind(&ChangeSelectionDialog::acceptCommand, this, CherryPick)); connect(m_revertButton, &QPushButton::clicked, this, std::bind(&ChangeSelectionDialog::acceptCommand, this, Revert)); connect(m_checkoutButton, &QPushButton::clicked, this, std::bind(&ChangeSelectionDialog::acceptCommand, this, Checkout)); connect(archiveButton, &QPushButton::clicked, this, std::bind(&ChangeSelectionDialog::acceptCommand, this, Archive)); if (id == "Git.Revert") m_revertButton->setDefault(true); else if (id == "Git.CherryPick") m_cherryPickButton->setDefault(true); else if (id == "Git.Checkout") m_checkoutButton->setDefault(true); else if (id == "Git.Archive") archiveButton->setDefault(true); else m_showButton->setDefault(true); m_changeModel = new QStringListModel(this); auto changeCompleter = new QCompleter(m_changeModel, this); m_changeNumberEdit->setCompleter(changeCompleter); changeCompleter->setCaseSensitivity(Qt::CaseInsensitive); recalculateDetails(); recalculateCompletion(); connect(closeButton, &QPushButton::clicked, this, &QDialog::reject); } ChangeSelectionDialog::~ChangeSelectionDialog() = default; QString ChangeSelectionDialog::change() const { return m_changeNumberEdit->text().trimmed(); } void ChangeSelectionDialog::selectCommitFromRecentHistory() { FilePath workingDir = workingDirectory(); if (workingDir.isEmpty()) return; QString commit = change(); int tilde = commit.indexOf('~'); if (tilde != -1) commit.truncate(tilde); LogChangeDialog dialog(LogChangeDialog::Select, this); dialog.setWindowTitle(Tr::tr("Select Commit")); dialog.runDialog(workingDir, commit, LogChangeWidget::IncludeRemotes); if (dialog.result() == QDialog::Rejected || dialog.commitIndex() == -1) return; m_changeNumberEdit->setText(dialog.commit()); } FilePath ChangeSelectionDialog::workingDirectory() const { const FilePath workingDir = m_workingDirectoryChooser->filePath(); if (workingDir.isEmpty() || !workingDir.exists()) return {}; return Core::VcsManager::findTopLevelForDirectory(workingDir); } ChangeCommand ChangeSelectionDialog::command() const { return m_command; } void ChangeSelectionDialog::acceptCommand(ChangeCommand command) { m_command = command; QDialog::accept(); } //! Set commit message in details void ChangeSelectionDialog::setDetails() { QPalette palette; if (m_process->result() == ProcessResult::FinishedWithSuccess) { const QString text = m_process->cleanedStdOut(); AnsiEscapeCodeHandler::setTextInDocument(m_detailsText->document(), text); palette.setColor(QPalette::Text, creatorColor(Theme::TextColorNormal)); m_changeNumberEdit->setPalette(palette); } else if (m_process->result() == ProcessResult::StartFailed) { m_detailsText->setPlainText(Tr::tr("Error: Could not start Git.")); } else { m_detailsText->setPlainText(Tr::tr("Error: Unknown reference")); palette.setColor(QPalette::Text, creatorColor(Theme::TextColorError)); m_changeNumberEdit->setPalette(palette); enableButtons(false); } } void ChangeSelectionDialog::enableButtons(bool b) { m_showButton->setEnabled(b); m_cherryPickButton->setEnabled(b); m_revertButton->setEnabled(b); m_checkoutButton->setEnabled(b); } void ChangeSelectionDialog::recalculateCompletion() { const FilePath workingDir = workingDirectory(); if (workingDir == m_oldWorkingDir) return; m_oldWorkingDir = workingDir; m_changeModel->setStringList(QStringList()); if (workingDir.isEmpty()) return; Process *process = new Process(this); process->setEnvironment(gitClient().processEnvironment(workingDir)); process->setCommand( {gitClient().vcsBinary(workingDir), {"for-each-ref", "--format=%(refname:short)"}}); process->setWorkingDirectory(workingDir); process->setUseCtrlCStub(true); connect(process, &Process::done, this, [this, process] { if (process->result() == ProcessResult::FinishedWithSuccess) m_changeModel->setStringList(process->cleanedStdOut().split('\n')); process->deleteLater(); }); process->start(); } void ChangeSelectionDialog::recalculateDetails() { enableButtons(true); const FilePath workingDir = workingDirectory(); if (workingDir.isEmpty()) { m_detailsText->setPlainText(Tr::tr("Error: Bad working directory.")); return; } const QString ref = change(); if (ref.isEmpty()) { m_detailsText->clear(); return; } m_process.reset(new Process); connect(m_process.get(), &Process::done, this, &ChangeSelectionDialog::setDetails); m_process->setWorkingDirectory(workingDir); m_process->setEnvironment(m_gitEnvironment); const ColorNames colors = GitClient::colorNames(); const QString showFormat = QStringLiteral( "--pretty=format:" "commit %C(%1)%H%Creset %C(%2)%d%Creset%n" "Author: %C(%3)%aN <%aE>%Creset%n" "Date: %C(%4)%ad (%ar)%Creset%n" "%n%C(%5)%s%Creset%n%n%b" ).arg(colors.hash, colors.decoration, colors.author, colors.date, colors.subject); m_process->setCommand({m_gitExecutable, {"show", "--decorate", "--stat=80", "--color=always", showFormat, ref}}); m_process->start(); m_detailsText->setPlainText(Tr::tr("Fetching commit data...")); } void ChangeSelectionDialog::changeTextChanged(const QString &text) { if (QCompleter *comp = m_changeNumberEdit->completer()) { if (text.isEmpty() && !comp->popup()->isVisible()) { comp->setCompletionPrefix(text); QTimer::singleShot(0, comp, [comp]{ comp->complete(); }); } } recalculateDetails(); } } // Git::Internal