File indexing completed on 2024-04-21 05:41:05
0001 /* 0002 SPDX-FileCopyrightText: 2019-2020 Nikolai Krasheninnikov <nkrasheninnikov@yandex.ru> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "svnlogdialog.h" 0008 0009 #include <QProcess> 0010 #include <QMenu> 0011 #include <QTemporaryFile> 0012 #include <QShortcut> 0013 0014 #include "svncommands.h" 0015 0016 namespace { 0017 0018 // Helper function: safe revert to a revision of a possibly modified file. If file could not be 0019 // reverted (for example, repo is inaccessible) it preserves current file. 0020 bool resetAndRevertFileToRevision(const QString &filePath, ulong revision) 0021 { 0022 QTemporaryFile file; 0023 if (!file.open()) { 0024 return false; 0025 } 0026 0027 bool preserveFile = true; 0028 QFile copyFile(filePath); 0029 if (!copyFile.open(QIODevice::ReadOnly)) { 0030 preserveFile = false; 0031 } else { 0032 const QByteArray data = copyFile.readAll(); 0033 if (file.write(data) != data.size() || !file.flush()) { 0034 preserveFile = false; 0035 } 0036 } 0037 0038 if (!SvnCommands::revertLocalChanges(filePath)) { 0039 return false; 0040 } 0041 if (!SvnCommands::revertToRevision(filePath, revision)) { 0042 if (preserveFile) { 0043 QFile::remove(filePath); 0044 QFile::copy(file.fileName(), filePath); 0045 } 0046 return false; 0047 } 0048 0049 return true; 0050 } 0051 0052 } 0053 0054 struct svnLogEntryInfo_t { 0055 svnLogEntryInfo_t() : 0056 remotePath(QString()), 0057 localPath(QString()), 0058 revision(0) 0059 {} 0060 0061 QString remotePath; ///< Affected remote path. 0062 QString localPath; ///< Affected local path. 0063 ulong revision; ///< Revision number. 0064 }; 0065 Q_DECLARE_METATYPE(svnLogEntryInfo_t); 0066 0067 enum columns_t { 0068 columnRevision, 0069 columnAuthor, 0070 columnDate, 0071 columnMessage 0072 }; 0073 0074 SvnLogDialog::SvnLogDialog(const QString& contextDir, QWidget *parent) : 0075 QDialog(parent), 0076 m_contextDir(contextDir), 0077 m_logLength(100) 0078 { 0079 m_ui.setupUi(this); 0080 0081 /* 0082 * Add actions, establish connections. 0083 */ 0084 QObject::connect(m_ui.pbOk, &QPushButton::clicked, this, &QWidget::close); 0085 QObject::connect(m_ui.pbRefresh, &QPushButton::clicked, this, &SvnLogDialog::refreshLog); 0086 QObject::connect(m_ui.pbNext100, &QPushButton::clicked, this, [this] () { 0087 m_logLength += 100; 0088 refreshLog(); 0089 } ); 0090 0091 QObject::connect(m_ui.tLog, &QWidget::customContextMenuRequested, this, &SvnLogDialog::showContextMenuLog); 0092 QObject::connect(m_ui.lPaths, &QWidget::customContextMenuRequested, this, &SvnLogDialog::showContextMenuChangesList); 0093 0094 m_updateToRev = new QAction(i18n("Update to revision"), this); 0095 m_updateToRev->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0096 QObject::connect(m_updateToRev, &QAction::triggered, this, &SvnLogDialog::updateRepoToRevision); 0097 0098 m_revertToRev = new QAction(i18n("Revert to revision"), this); 0099 m_revertToRev->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); 0100 QObject::connect(m_revertToRev, &QAction::triggered, this, &SvnLogDialog::revertRepoToRevision); 0101 0102 m_diffFilePrev = new QAction(i18n("Show changes"), this); 0103 m_diffFilePrev->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); 0104 QObject::connect(m_diffFilePrev, &QAction::triggered, this, [this] () { 0105 svnLogEntryInfo_t info = m_diffFilePrev->data().value<svnLogEntryInfo_t>(); 0106 Q_EMIT diffBetweenRevs(info.remotePath, info.revision, info.revision - 1); 0107 } ); 0108 0109 m_diffFileCurrent = new QAction(i18n("Changes against working copy"), this); 0110 m_diffFileCurrent->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); 0111 QObject::connect(m_diffFileCurrent, &QAction::triggered, this, [this] () { 0112 svnLogEntryInfo_t info = m_diffFileCurrent->data().value<svnLogEntryInfo_t>(); 0113 Q_EMIT diffAgainstWorkingCopy(info.localPath, info.revision); 0114 } ); 0115 0116 m_fileRevertToRev = new QAction(i18n("Revert to revision"), this); 0117 m_fileRevertToRev->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); 0118 QObject::connect(m_fileRevertToRev, &QAction::triggered, this, &SvnLogDialog::revertFileToRevision); 0119 0120 QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, this); 0121 QObject::connect(refreshShortcut, &QShortcut::activated, this, &SvnLogDialog::refreshLog); 0122 refreshShortcut->setAutoRepeat(false); 0123 0124 /* 0125 * Additional setup. 0126 */ 0127 m_ui.tLog->horizontalHeader()->setSectionResizeMode(columnDate, QHeaderView::ResizeToContents); 0128 0129 refreshLog(); 0130 } 0131 0132 SvnLogDialog::~SvnLogDialog() = default; 0133 0134 void SvnLogDialog::setCurrentRevision(ulong revision) 0135 { 0136 if (m_log.isNull()) { 0137 return; 0138 } 0139 0140 for (int i = 0; i < m_log->size(); ++i) { 0141 if (m_log->at(i).revision == revision) { 0142 QFont font; 0143 font.setBold(true); 0144 0145 m_ui.tLog->item(i, columnRevision)->setFont(font); 0146 m_ui.tLog->item(i, columnAuthor)->setFont(font); 0147 m_ui.tLog->item(i, columnDate)->setFont(font); 0148 m_ui.tLog->item(i, columnMessage)->setFont(font); 0149 0150 m_ui.tLog->selectRow(i); 0151 0152 break; 0153 } 0154 } 0155 } 0156 0157 void SvnLogDialog::refreshLog() 0158 { 0159 m_log = SvnCommands::getLog(m_contextDir, m_logLength); 0160 0161 if (m_log.isNull()) { 0162 return; 0163 } 0164 0165 m_ui.tLog->clearContents(); 0166 m_ui.teMessage->clear(); 0167 m_ui.lPaths->clear(); 0168 0169 m_ui.tLog->setRowCount( m_log->size() ); 0170 for (int i = 0; i < m_log->size(); ++i) { 0171 QTableWidgetItem *revision = new QTableWidgetItem(QString::number(m_log->at(i).revision)); 0172 QTableWidgetItem *author = new QTableWidgetItem(m_log->at(i).author); 0173 QTableWidgetItem *date = new QTableWidgetItem(m_log->at(i).date.toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"))); 0174 QTableWidgetItem *msg = new QTableWidgetItem(m_log->at(i).msg); 0175 0176 revision->setData(Qt::UserRole, QVariant::fromValue(m_log->at(i).revision)); 0177 0178 m_ui.tLog->setItem(i, columnRevision, revision); 0179 m_ui.tLog->setItem(i, columnAuthor, author); 0180 m_ui.tLog->setItem(i, columnDate, date); 0181 m_ui.tLog->setItem(i, columnMessage, msg); 0182 } 0183 0184 SvnLogDialog::setCurrentRevision( SvnCommands::localRevision(m_contextDir) ); 0185 } 0186 0187 void SvnLogDialog::on_tLog_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) 0188 { 0189 Q_UNUSED(currentColumn) 0190 Q_UNUSED(previousRow) 0191 Q_UNUSED(previousColumn) 0192 0193 if (currentRow < 0) { 0194 return; 0195 } 0196 if (m_log.isNull()) { 0197 return; 0198 } 0199 if (m_log->size() < currentRow) { 0200 return; 0201 } 0202 if (m_log->empty()) { 0203 return; 0204 } 0205 0206 const QString rootUrl = SvnCommands::remoteRootUrl(m_contextDir); 0207 if (rootUrl.isEmpty()) { 0208 return; 0209 } 0210 0211 m_ui.teMessage->setPlainText( m_log->at(currentRow).msg ); 0212 m_ui.lPaths->clear(); 0213 0214 for (const auto &i : std::as_const(m_log->at(currentRow).affectedPaths)) { 0215 svnLogEntryInfo_t info; 0216 info.remotePath = rootUrl + i.path; 0217 info.localPath = m_contextDir + i.path; 0218 info.revision = m_log->at(currentRow).revision; 0219 0220 // For user: let's show relative path from 'svn log'. 0221 QListWidgetItem *item = new QListWidgetItem(i.path, m_ui.lPaths); 0222 item->setData(Qt::UserRole, QVariant::fromValue(info)); 0223 m_ui.lPaths->addItem(item); 0224 } 0225 } 0226 0227 void SvnLogDialog::showContextMenuLog(const QPoint &pos) 0228 { 0229 QTableWidgetItem *item = m_ui.tLog->item( m_ui.tLog->currentRow(), columnRevision ); 0230 if (item == nullptr) { 0231 return; 0232 } 0233 0234 m_updateToRev->setData( item->data(Qt::UserRole) ); 0235 m_revertToRev->setData( item->data(Qt::UserRole) ); 0236 0237 QMenu *menu = new QMenu(this); 0238 menu->addAction(m_updateToRev); 0239 menu->addAction(m_revertToRev); 0240 0241 // Adjust popup menu position for QTableWidget header height. 0242 const QPoint popupPoint = QPoint(pos.x(), pos.y() + m_ui.tLog->horizontalHeader()->height()); 0243 menu->exec( m_ui.tLog->mapToGlobal(popupPoint) ); 0244 } 0245 0246 void SvnLogDialog::showContextMenuChangesList(const QPoint &pos) 0247 { 0248 QListWidgetItem *item = m_ui.lPaths->currentItem(); 0249 if (item == nullptr) { 0250 return; 0251 } 0252 const svnLogEntryInfo_t info = item->data(Qt::UserRole).value<svnLogEntryInfo_t>(); 0253 0254 m_diffFilePrev->setData( QVariant::fromValue(info) ); 0255 m_diffFileCurrent->setData( QVariant::fromValue(info) ); 0256 m_fileRevertToRev->setData( QVariant::fromValue(info) ); 0257 0258 QMenu *menu = new QMenu(this); 0259 menu->addAction(m_diffFilePrev); 0260 menu->addAction(m_diffFileCurrent); 0261 menu->addAction(m_fileRevertToRev); 0262 0263 menu->exec( m_ui.lPaths->mapToGlobal(pos) ); 0264 } 0265 0266 void SvnLogDialog::updateRepoToRevision() 0267 { 0268 bool convert = false; 0269 uint revision = m_updateToRev->data().toUInt(&convert); 0270 if (!convert || !SvnCommands::updateToRevision(m_contextDir, revision)) { 0271 Q_EMIT errorMessage(i18nc("@info:status", "SVN log: update to revision failed.")); 0272 } else { 0273 Q_EMIT operationCompletedMessage(i18nc("@info:status", "SVN log: update to revision %1 successful.", revision)); 0274 SvnLogDialog::refreshLog(); 0275 } 0276 } 0277 0278 void SvnLogDialog::revertRepoToRevision() 0279 { 0280 bool convert = false; 0281 uint revision = m_revertToRev->data().toUInt(&convert); 0282 if (!convert || !SvnCommands::revertToRevision(m_contextDir, revision)) { 0283 Q_EMIT errorMessage(i18nc("@info:status", "SVN log: revert to revision failed.")); 0284 } else { 0285 Q_EMIT operationCompletedMessage(i18nc("@info:status", "SVN log: revert to revision %1 successful.", revision)); 0286 } 0287 } 0288 0289 void SvnLogDialog::revertFileToRevision() 0290 { 0291 svnLogEntryInfo_t info = m_fileRevertToRev->data().value<svnLogEntryInfo_t>(); 0292 0293 if (!resetAndRevertFileToRevision(info.localPath, info.revision)) { 0294 Q_EMIT errorMessage(i18nc("@info:status", "SVN log: revert to revision failed.")); 0295 } else { 0296 Q_EMIT operationCompletedMessage(i18nc("@info:status", "SVN log: revert to revision %1 successful.", info.revision)); 0297 } 0298 } 0299 0300 #include "moc_svnlogdialog.cpp"