1 /*
2     SPDX-FileCopyrightText: 2017 René J.V. Bertin <rjvbertin@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "difflistmodel.h"
8 #include "phabricatorjobs.h"
9 
10 #include <QBrush>
11 #include <QDebug>
12 #include <QDir>
13 #include <QTemporaryDir>
14 
DiffListModel(QObject * parent)15 DiffListModel::DiffListModel(QObject *parent)
16     : QAbstractListModel(parent)
17     , m_initialDir(QDir::currentPath())
18     , m_tempDir(nullptr)
19 {
20     refresh();
21 }
22 
refresh()23 void DiffListModel::refresh()
24 {
25     if (m_tempDir) {
26         qCritical() << "DiffListModel::refresh() called while still active!";
27         return;
28     }
29 
30     beginResetModel();
31     m_values.clear();
32     endResetModel();
33 
34     // our CWD should be the directory from which the application was launched, which
35     // may or may not be a git, mercurial or svn working copy, so we create a temporary
36     // directory in which we initialise a git repository. This may be an empty repo.
37 
38     m_initialDir = QDir::currentPath();
39     m_tempDir = new QTemporaryDir;
40     if (!m_tempDir->isValid()) {
41         qCritical() << "DiffListModel::refresh() failed to create temporary directory" << m_tempDir->path() << ":" << m_tempDir->errorString();
42     } else {
43         if (QDir::setCurrent(m_tempDir->path())) {
44             // the directory will be removed in receivedDiffRevs()
45             m_tempDir->setAutoRemove(false);
46             QProcess initGit;
47             bool ok = false;
48             // create the virgin git repo. This is a very cheap operation that should
49             // never fail in a fresh temporary directory we ourselves created, so it
50             // should be OK to do this with a synchronous call.
51             initGit.start(QLatin1String("git init"), QStringList());
52             if (initGit.waitForStarted(1000)) {
53                 ok = initGit.waitForFinished(500);
54             }
55             if (!ok) {
56                 qCritical() << "DiffListModel::refresh() : couldn't create temp. git repo:" << initGit.errorString();
57             }
58         } else {
59             qCritical() << "DiffListModel::refresh() failed to chdir to" << m_tempDir->path();
60         }
61     }
62     // create a list request with the current (= temp.) directory as the project directory.
63     // This request is executed asynchronously, which is why we cannot restore the initial
64     // working directory just yet, nor remove the temporary directory.
65     Phabricator::DiffRevList *repo = new Phabricator::DiffRevList(QDir::currentPath(), this);
66     connect(repo, &Phabricator::DiffRevList::finished, this, &DiffListModel::receivedDiffRevs);
67     repo->start();
68 }
69 
receivedDiffRevs(KJob * job)70 void DiffListModel::receivedDiffRevs(KJob *job)
71 {
72     if (job->error() != 0) {
73         qWarning() << "error getting differential revision list" << job->errorString();
74         beginResetModel();
75         m_values.clear();
76         endResetModel();
77         return;
78     }
79 
80     const auto diffRevList = dynamic_cast<Phabricator::DiffRevList *>(job);
81     const auto revs = diffRevList->reviews();
82 
83     beginResetModel();
84     m_values.clear();
85     for (const auto review : revs) {
86         auto status = diffRevList->statusMap()[review.second];
87         m_values += Value{review.second, review.first, status};
88     }
89     endResetModel();
90 
91     // now we can restore the initial working directory and remove the temp directory
92     // (in that order!).
93     if (!QDir::setCurrent(m_initialDir)) {
94         qCritical() << "DiffListModel::receivedDiffRevs() failed to restore initial directory" << m_initialDir;
95     }
96     if (m_tempDir) {
97         m_tempDir->remove();
98         delete m_tempDir;
99         m_tempDir = nullptr;
100     }
101 }
102 
roleNames() const103 QHash<int, QByteArray> DiffListModel::roleNames() const
104 {
105     const QHash<int, QByteArray> roles = {{Qt::DisplayRole, QByteArrayLiteral("display")},
106                                           {Qt::ToolTipRole, QByteArrayLiteral("toolTip")},
107                                           {Qt::ForegroundRole, QByteArrayLiteral("textColor")}};
108     return roles;
109 }
110 
data(const QModelIndex & idx,int role) const111 QVariant DiffListModel::data(const QModelIndex &idx, int role) const
112 {
113     if (!idx.isValid() || idx.column() != 0 || idx.row() >= m_values.size()) {
114         return QVariant();
115     }
116 
117     switch (role) {
118     case Qt::DisplayRole:
119         return m_values[idx.row()].summary;
120     case Qt::ToolTipRole:
121         return m_values[idx.row()].id;
122     case Qt::ForegroundRole:
123         // Use the colours arc also uses
124         switch (m_values[idx.row()].status.value<Phabricator::DiffRevList::Status>()) {
125         case Phabricator::DiffRevList::Accepted:
126             // alternative: KColorScheme::ForegroundRole::PositiveText
127             return QBrush(Qt::green);
128         case Phabricator::DiffRevList::NeedsReview:
129             // alternative: KColorScheme::ForegroundRole::NeutralText
130             return QBrush(Qt::magenta);
131         case Phabricator::DiffRevList::NeedsRevision:
132             // alternative: KColorScheme::ForegroundRole::NegativeText
133             return QBrush(Qt::red);
134         default:
135             return {};
136         }
137     }
138     return {};
139 }
140 
rowCount(const QModelIndex & parent) const141 int DiffListModel::rowCount(const QModelIndex &parent) const
142 {
143     return parent.isValid() ? 0 : m_values.count();
144 }
145 
get(int row,const QByteArray & role)146 QVariant DiffListModel::get(int row, const QByteArray &role)
147 {
148     return index(row, 0).data(roleNames().key(role));
149 }
150 
setStatus(const QString & status)151 void DiffListModel::setStatus(const QString &status)
152 {
153     if (m_status != status) {
154         m_status = status;
155         refresh();
156     }
157 }
158