1#include "MergeWidget.h"
2
3#include <CommitInfo.h>
4#include <FileDiffWidget.h>
5#include <FileEditor.h>
6#include <GitBase.h>
7#include <GitCache.h>
8#include <GitLocal.h>
9#include <GitMerge.h>
10#include <GitQlientStyles.h>
11#include <GitRemote.h>
12#include <GitWip.h>
13#include <QPinnableTabWidget.h>
14#include <RevisionFiles.h>
15
16#include <QFile>
17#include <QLabel>
18#include <QLineEdit>
19#include <QListWidget>
20#include <QMessageBox>
21#include <QPushButton>
22#include <QStackedWidget>
23#include <QTextEdit>
24#include <QVBoxLayout>
25
26MergeWidget::MergeWidget(const QSharedPointer<GitCache> &gitQlientCache, const QSharedPointer<GitBase> &git,
27 QWidget *parent)
28 : QFrame(parent)
29 , mGitQlientCache(gitQlientCache)
30 , mGit(git)
31 , mConflictFiles(new QListWidget())
32 , mMergedFiles(new QListWidget())
33 , mCommitTitle(new QLineEdit())
34 , mDescription(new QTextEdit())
35 , mMergeBtn(new QPushButton(tr("Merge && Commit")))
36 , mAbortBtn(new QPushButton(tr("Abort merge")))
37 , mStacked(new QStackedWidget())
38 , mFileDiff(new FileDiffWidget(mGit, mGitQlientCache))
39{
40 mCommitTitle->setObjectName("leCommitTitle");
41
42 mDescription->setMaximumHeight(125);
43 mDescription->setPlaceholderText(tr("Description"));
44 mDescription->setObjectName("teDescription");
45 mDescription->setLineWrapMode(QTextEdit::WidgetWidth);
46 mDescription->setReadOnly(false);
47 mDescription->setAcceptRichText(false);
48
49 mAbortBtn->setObjectName("warningButton");
50 mMergeBtn->setObjectName("applyActionBtn");
51
52 const auto mergeBtnLayout = new QHBoxLayout();
53 mergeBtnLayout->setContentsMargins(QMargins());
54 mergeBtnLayout->addWidget(mAbortBtn);
55 mergeBtnLayout->addStretch();
56 mergeBtnLayout->addWidget(mMergeBtn);
57
58 const auto mergeInfoLayout = new QVBoxLayout();
59 mergeInfoLayout->setContentsMargins(QMargins());
60 mergeInfoLayout->setSpacing(0);
61 mergeInfoLayout->addWidget(mCommitTitle);
62 mergeInfoLayout->addWidget(mDescription);
63 mergeInfoLayout->addSpacerItem(new QSpacerItem(1, 10, QSizePolicy::Fixed, QSizePolicy::Fixed));
64 mergeInfoLayout->addLayout(mergeBtnLayout);
65
66 const auto mergeFrame = new QFrame();
67 mergeFrame->setObjectName("mergeFrame");
68
69 const auto conflictsLabel = new QLabel(tr("Conflicts"));
70 conflictsLabel->setObjectName("FilesListTitle");
71
72 const auto automergeLabel = new QLabel(tr("Changes to be committed"));
73 automergeLabel->setObjectName("FilesListTitle");
74
75 const auto mergeLayout = new QVBoxLayout(mergeFrame);
76 mergeLayout->setContentsMargins(QMargins());
77 mergeLayout->setSpacing(0);
78 mergeLayout->addWidget(conflictsLabel);
79 mergeLayout->addWidget(mConflictFiles);
80 mergeLayout->addStretch(1);
81 mergeLayout->addWidget(automergeLabel);
82 mergeLayout->addWidget(mMergedFiles);
83 mergeLayout->addStretch(2);
84 mergeLayout->addLayout(mergeInfoLayout);
85
86 mFileDiff->hideBackButton();
87
88 const auto noFileFrame = new QFrame();
89 const auto noFileLayout = new QGridLayout();
90 noFileLayout->setContentsMargins(0, 0, 0, 0);
91 noFileLayout->setSpacing(0);
92 noFileLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), 0, 0);
93 noFileLayout->addWidget(new QLabel(tr("Select a file from the list to show its contents.")), 1, 1);
94 noFileLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 2);
95 noFileFrame->setLayout(noFileLayout);
96
97 mStacked->insertWidget(0, noFileFrame);
98 mStacked->insertWidget(1, mFileDiff);
99
100 const auto layout = new QHBoxLayout(this);
101 layout->setContentsMargins(QMargins());
102 layout->addWidget(mergeFrame);
103 layout->addWidget(mStacked);
104
105 connect(mFileDiff, &FileDiffWidget::exitRequested, this, [this]() { mStacked->setCurrentIndex(0); });
106 connect(mFileDiff, &FileDiffWidget::fileStaged, this, &MergeWidget::onConflictResolved);
107
108 connect(mConflictFiles, &QListWidget::itemClicked, this, &MergeWidget::changeDiffView);
109 connect(mConflictFiles, &QListWidget::itemDoubleClicked, this, &MergeWidget::changeDiffView);
110 connect(mMergedFiles, &QListWidget::itemClicked, this, &MergeWidget::changeDiffView);
111 connect(mMergedFiles, &QListWidget::itemDoubleClicked, this, &MergeWidget::changeDiffView);
112 connect(mAbortBtn, &QPushButton::clicked, this, &MergeWidget::abort);
113 connect(mMergeBtn, &QPushButton::clicked, this, &MergeWidget::commit);
114}
115
116void MergeWidget::configure(const RevisionFiles &files, ConflictReason reason)
117{
118 mReason = reason;
119
120 mConflictFiles->clear();
121 mMergedFiles->clear();
122 mFileDiff->clear();
123
124 QFile mergeMsg(QString(mGit->getGitDir() + QString::fromUtf8("/MERGE_MSG")));
125
126 if (mergeMsg.open(QIODevice::ReadOnly))
127 {
128 const auto summary = QString::fromUtf8(mergeMsg.readLine()).trimmed();
129 const auto description = QString::fromUtf8(mergeMsg.readAll()).trimmed();
130 mCommitTitle->setText(summary);
131 mDescription->setText(description);
132 mergeMsg.close();
133 }
134
135 fillButtonFileList(files);
136}
137
138void MergeWidget::configureForCherryPick(const RevisionFiles &files, const QStringList &pendingShas)
139{
140 mReason = ConflictReason::CherryPick;
141 mPendingShas = pendingShas;
142
143 mConflictFiles->clear();
144 mMergedFiles->clear();
145 mFileDiff->clear();
146
147 QFile mergeMsg(QString(mGit->getGitDir() + QString::fromUtf8("/MERGE_MSG")));
148
149 if (mergeMsg.open(QIODevice::ReadOnly))
150 {
151 const auto summary = QString::fromUtf8(mergeMsg.readLine()).trimmed();
152 const auto description = QString::fromUtf8(mergeMsg.readAll()).trimmed();
153 mCommitTitle->setText(summary);
154 mDescription->setText(description);
155 mergeMsg.close();
156 }
157
158 fillButtonFileList(files);
159}
160
161void MergeWidget::fillButtonFileList(const RevisionFiles &files)
162{
163 for (auto i = 0; i < files.count(); ++i)
164 {
165 const auto fileName = files.getFile(i);
166 const auto fileInConflict = files.statusCmp(i, RevisionFiles::CONFLICT);
167 const auto item = new QListWidgetItem(fileName);
168 item->setData(Qt::UserRole, fileInConflict);
169
170 fileInConflict ? mConflictFiles->addItem(item) : mMergedFiles->addItem(item);
171 }
172}
173
174void MergeWidget::changeDiffView(QListWidgetItem *item)
175{
176 const auto file = item->text();
177 const auto wip = mGitQlientCache->commitInfo(CommitInfo::ZERO_SHA);
178
179 const auto configured
180 = mFileDiff->configure(CommitInfo::ZERO_SHA, wip.firstParent(), mGit->getWorkingDir() + "/" + file, false);
181
182 mStacked->setCurrentIndex(configured);
183
184 if (!configured)
185 QMessageBox::warning(this, tr("No diff to show"), tr("There is not diff information to be shown."));
186}
187
188void MergeWidget::abort()
189{
190 GitExecResult ret;
191
192 switch (mReason)
193 {
194 case ConflictReason::Pull:
195 case ConflictReason::Merge: {
196 QScopedPointer<GitMerge> git(new GitMerge(mGit, mGitQlientCache));
197 ret = git->abortMerge();
198 break;
199 }
200 case ConflictReason::CherryPick: {
201 QScopedPointer<GitLocal> git(new GitLocal(mGit));
202 ret = git->cherryPickAbort();
203 break;
204 }
205 default:
206 break;
207 }
208
209 if (!ret.success)
210 {
211 QMessageBox msgBox(QMessageBox::Critical, tr("Error aborting"),
212 tr("There were problems during the aborting the merge. Please, see the detailed "
213 "description for more information."),
214 QMessageBox::Ok, this);
215 msgBox.setDetailedText(ret.output);
216 msgBox.setStyleSheet(GitQlientStyles::getStyles());
217 msgBox.exec();
218 }
219 else
220 {
221 mPendingShas.clear();
222 removeMergeComponents();
223
224 emit signalMergeFinished();
225 }
226}
227
228void MergeWidget::commit()
229{
230 GitExecResult ret;
231
232 switch (mReason)
233 {
234 case ConflictReason::Pull:
235 case ConflictReason::Merge: {
236 QScopedPointer<GitMerge> git(new GitMerge(mGit, mGitQlientCache));
237 ret = git->applyMerge();
238 break;
239 }
240 case ConflictReason::CherryPick: {
241 QScopedPointer<GitLocal> git(new GitLocal(mGit));
242 ret = git->cherryPickContinue();
243 break;
244 }
245 default:
246 break;
247 }
248
249 if (!ret.success)
250 {
251 QMessageBox msgBox(QMessageBox::Critical, tr("Error while merging"),
252 tr("There were problems during the merge operation. Please, see the detailed description "
253 "for more information."),
254 QMessageBox::Ok, this);
255 msgBox.setDetailedText(ret.output);
256 msgBox.setStyleSheet(GitQlientStyles::getStyles());
257 msgBox.exec();
258 }
259 else
260 {
261 removeMergeComponents();
262
263 if (!mPendingShas.isEmpty()) { }
264
265 emit signalMergeFinished();
266 }
267}
268
269void MergeWidget::removeMergeComponents()
270{
271 mCommitTitle->clear();
272 mDescription->clear();
273
274 mConflictFiles->clear();
275 mMergedFiles->clear();
276 mFileDiff->clear();
277}
278
279void MergeWidget::onConflictResolved(const QString &)
280{
281 const auto currentRow = mConflictFiles->currentRow();
282 const auto currentConflict = mConflictFiles->takeItem(currentRow);
283
284 if (currentConflict)
285 {
286 const auto fileName = currentConflict->text();
287 delete currentConflict;
288
289 mMergedFiles->addItem(fileName);
290 }
291
292 mConflictFiles->clearSelection();
293 mConflictFiles->selectionModel()->clearSelection();
294 mConflictFiles->selectionModel()->clearCurrentIndex();
295
296 mFileDiff->clear();
297 mStacked->setCurrentIndex(0);
298}
299
300void MergeWidget::cherryPickCommit()
301{
302 auto shas = mPendingShas;
303 for (const auto &sha : qAsConst(mPendingShas))
304 {
305 QScopedPointer<GitLocal> git(new GitLocal(mGit));
306 const auto ret = git->cherryPickCommit(sha);
307
308 shas.takeFirst();
309
310 if (ret.success && shas.isEmpty())
311 emit signalMergeFinished();
312 else if (!ret.success)
313 {
314 const auto errorMsg = ret.output;
315
316 if (errorMsg.contains("error: could not apply", Qt::CaseInsensitive)
317 && errorMsg.contains("after resolving the conflicts", Qt::CaseInsensitive))
318 {
319 const auto wipCommit = mGitQlientCache->commitInfo(CommitInfo::ZERO_SHA);
320
321 QScopedPointer<GitWip> git(new GitWip(mGit, mGitQlientCache));
322 git->updateWip();
323
324 const auto files = mGitQlientCache->revisionFile(CommitInfo::ZERO_SHA, wipCommit.firstParent());
325
326 if (files)
327 configureForCherryPick(files.value(), shas);
328 }
329 else
330 {
331 QMessageBox msgBox(QMessageBox::Critical, tr("Error while cherry-pick"),
332 tr("There were problems during the cherry-pich operation. Please, see the detailed "
333 "description for more information."),
334 QMessageBox::Ok, this);
335 msgBox.setDetailedText(errorMsg);
336 msgBox.setStyleSheet(GitQlientStyles::getStyles());
337 msgBox.exec();
338
339 mPendingShas.clear();
340
341 emit signalMergeFinished();
342 }
343 }
344 }
345}
346