File indexing completed on 2024-04-28 05:49:10

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "pushpulldialog.h"
0007 
0008 #include <QFile>
0009 #include <QProcess>
0010 #include <QSettings>
0011 
0012 #include <KConfigGroup>
0013 #include <KSharedConfig>
0014 #include <KTextEditor/MainWindow>
0015 #include <KTextEditor/View>
0016 
0017 #include <gitprocess.h>
0018 #include <hostprocess.h>
0019 #include <ktexteditor_utils.h>
0020 
0021 PushPullDialog::PushPullDialog(KTextEditor::MainWindow *mainWindow, const QString &repoPath)
0022     : HUDDialog(nullptr, mainWindow->window())
0023     , m_repo(repoPath)
0024 {
0025     m_lineEdit.setFont(Utils::editorFont());
0026     m_treeView.setFont(Utils::editorFont());
0027     setFilteringEnabled(false);
0028     loadLastExecutedCommands();
0029     detectGerrit();
0030 }
0031 
0032 void PushPullDialog::openDialog(PushPullDialog::Mode m)
0033 {
0034     // build the string
0035     QStringList builtStrings;
0036     if (m == Push && m_isGerrit) {
0037         builtStrings << QStringLiteral("git push origin HEAD:refs/for/%1").arg(m_gerritBranch);
0038     } else {
0039         builtStrings = buildCmdStrings(m);
0040     }
0041     // find if we have a last executed push/pull command
0042     QString lastCmd = getLastPushPullCmd(m);
0043 
0044     QStringList lastExecCmds = m_lastExecutedCommands;
0045 
0046     // if found, bring it up
0047     if (!lastCmd.isEmpty()) {
0048         lastExecCmds.removeAll(lastCmd);
0049         lastExecCmds.push_front(lastCmd);
0050     }
0051 
0052     for (const auto &s : builtStrings) {
0053         lastExecCmds.removeAll(s);
0054         lastExecCmds.push_front(s);
0055     }
0056 
0057     setStringList(lastExecCmds);
0058 
0059     connect(m_treeView.selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex &current, const QModelIndex &) {
0060         m_lineEdit.setText(current.data().toString());
0061     });
0062 
0063     reselectFirst();
0064 
0065     exec();
0066 }
0067 
0068 QString PushPullDialog::getLastPushPullCmd(Mode m) const
0069 {
0070     const QString cmdToFind = m == Push ? QStringLiteral("git push") : QStringLiteral("git pull");
0071     QString found;
0072     for (const auto &cmd : m_lastExecutedCommands) {
0073         if (cmd.startsWith(cmdToFind)) {
0074             found = cmd;
0075             break;
0076         }
0077     }
0078     return found;
0079 }
0080 
0081 void PushPullDialog::loadLastExecutedCommands()
0082 {
0083     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("kategit"));
0084     m_lastExecutedCommands = config.readEntry("lastExecutedGitCmds", QStringList());
0085 }
0086 
0087 void PushPullDialog::saveCommand(const QString &command)
0088 {
0089     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("kategit"));
0090     QStringList cmds = m_lastExecutedCommands;
0091     cmds.removeAll(command);
0092     cmds.push_front(command);
0093     while (cmds.size() > 8) {
0094         cmds.pop_back();
0095     }
0096     config.writeEntry("lastExecutedGitCmds", cmds);
0097 }
0098 
0099 /**
0100  * This is not for display, hence not reusing gitutils here
0101  */
0102 static QString currentBranchName(const QString &repo)
0103 {
0104     QProcess git;
0105     if (!setupGitProcess(git, repo, {QStringLiteral("symbolic-ref"), QStringLiteral("--short"), QStringLiteral("HEAD")})) {
0106         return {};
0107     }
0108 
0109     startHostProcess(git, QIODevice::ReadOnly);
0110     if (git.waitForStarted() && git.waitForFinished(-1)) {
0111         if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
0112             return QString::fromUtf8(git.readAllStandardOutput().trimmed());
0113         }
0114     }
0115     // give up
0116     return {};
0117 }
0118 
0119 static QStringList remotesList(const QString &repo)
0120 {
0121     QProcess git;
0122     if (!setupGitProcess(git, repo, {QStringLiteral("remote")})) {
0123         return {};
0124     }
0125 
0126     startHostProcess(git, QIODevice::ReadOnly);
0127     if (git.waitForStarted() && git.waitForFinished(-1)) {
0128         if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
0129             return QString::fromUtf8(git.readAllStandardOutput()).split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0130         }
0131     }
0132     return {};
0133 }
0134 
0135 static QString getRemoteForCurrentBranch(const QString &repo, const QString &branch)
0136 {
0137     QProcess git;
0138     const QStringList args{QStringLiteral("config"), QStringLiteral("branch.%1.remote").arg(branch)};
0139     if (!setupGitProcess(git, repo, args)) {
0140         return {};
0141     }
0142 
0143     startHostProcess(git, QIODevice::ReadOnly);
0144     if (git.waitForStarted() && git.waitForFinished(-1)) {
0145         if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
0146             return QString::fromUtf8(git.readAllStandardOutput().trimmed());
0147         }
0148     }
0149     return {};
0150 }
0151 
0152 QStringList PushPullDialog::buildCmdStrings(Mode m)
0153 {
0154     const QString arg = m == Push ? QLatin1String("push") : QLatin1String("pull");
0155     const auto br = currentBranchName(m_repo);
0156     if (br.isEmpty()) {
0157         return {QStringLiteral("git %1").arg(arg)};
0158     }
0159 
0160     auto remoteForBranch = getRemoteForCurrentBranch(m_repo, br);
0161     if (remoteForBranch.isEmpty()) {
0162         const auto remotes = remotesList(m_repo);
0163         if (remotes.isEmpty()) {
0164             return {QStringLiteral("git %1").arg(arg)};
0165         }
0166         QStringList cmds;
0167         // reverse traversal as later, these commands will be pushed in front of the
0168         // list displayed to user, so we invert the order here and it will appear in
0169         // the same order that git shows
0170         for (auto ri = remotes.crbegin(); ri != remotes.crend(); ++ri) {
0171             cmds << QStringLiteral("git %1 %2 %3").arg(arg, *ri, br);
0172         }
0173         return cmds;
0174     } else {
0175         // if we found a remote, only offer that
0176         return {QStringLiteral("git %1 %2 %3").arg(arg, remoteForBranch, br)};
0177     }
0178 }
0179 
0180 void PushPullDialog::slotReturnPressed(const QModelIndex &)
0181 {
0182     if (!m_lineEdit.text().isEmpty()) {
0183         auto args = m_lineEdit.text().split(QLatin1Char(' '));
0184         if (args.first() == QStringLiteral("git")) {
0185             saveCommand(m_lineEdit.text());
0186             args.pop_front();
0187             Q_EMIT runGitCommand(args);
0188         }
0189     }
0190 
0191     hide();
0192 }
0193 
0194 void PushPullDialog::detectGerrit()
0195 {
0196     if (QFile::exists(m_repo + QLatin1String(".gitreview"))) {
0197         m_isGerrit = true;
0198         QSettings s(m_repo + QLatin1String("/.gitreview"), QSettings::IniFormat);
0199         m_gerritBranch = s.value(QStringLiteral("gerrit/defaultbranch")).toString();
0200     }
0201 }
0202 
0203 #include "moc_pushpulldialog.cpp"