1#include "SquashDlg.h"
2#include "ui_SquashDlg.h"
3
4#include <CheckBox.h>
5#include <GitBase.h>
6#include <GitBranches.h>
7#include <GitCache.h>
8#include <GitLocal.h>
9#include <GitMerge.h>
10#include <GitQlientSettings.h>
11#include <GitWip.h>
12
13#include <QLabel>
14#include <QMessageBox>
15#include <QUuid>
16
17SquashDlg::SquashDlg(const QSharedPointer<GitBase> git, const QSharedPointer<GitCache> &cache, const QStringList &shas,
18 QWidget *parent)
19 : QDialog(parent)
20 , mGit(git)
21 , mCache(cache)
22 , mShas(shas)
23 , ui(new Ui::SquashDlg)
24{
25 ui->setupUi(this);
26
27 setAttribute(Qt::WA_DeleteOnClose);
28
29 mTitleMaxLength = GitQlientSettings().globalValue("commitTitleMaxLength", mTitleMaxLength).toInt();
30
31 ui->lCounter->setText(QString::number(mTitleMaxLength));
32 ui->leCommitTitle->setMaxLength(mTitleMaxLength);
33
34 auto description = QString("This is a combination of %1 commits:\n\n").arg(shas.count());
35
36 const auto commitsLayout = new QGridLayout();
37 commitsLayout->setContentsMargins(10, 10, 10, 10);
38 commitsLayout->setSpacing(10);
39 commitsLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
40
41 auto row = 0;
42 for (const auto &sha : shas)
43 {
44 const auto shortSha = sha.left(8);
45 const auto commitTitle = mCache->commitInfo(sha).shortLog;
46 description.append(QString("Commit %1: %2 - %3\n\n").arg(row + 1).arg(shortSha, commitTitle));
47
48 commitsLayout->addWidget(new QLabel(QString("<strong>(%1)</strong>").arg(shortSha)), row, 0);
49 commitsLayout->addWidget(new QLabel(commitTitle), row, 1);
50 ++row;
51 }
52
53 commitsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), row, 0);
54
55 ui->commitsFrame->setLayout(commitsLayout);
56 ui->scrollArea->setWidgetResizable(true);
57 ui->teDescription->setText(description);
58
59 connect(ui->leCommitTitle, &QLineEdit::textChanged, this, &SquashDlg::updateCounter);
60 connect(ui->leCommitTitle, &QLineEdit::returnPressed, this, &SquashDlg::accept);
61}
62
63SquashDlg::~SquashDlg()
64{
65 delete ui;
66}
67
68void SquashDlg::accept()
69{
70 QString msg;
71
72 if (checkMsg(msg))
73 {
74 const auto revInfo = mCache->commitInfo(CommitInfo::ZERO_SHA);
75
76 QScopedPointer<GitWip> git(new GitWip(mGit, mCache));
77 git->updateWip();
78
79 const auto lastChild = mCache->commitInfo(mShas.last());
80
81 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
82
83 if (lastChild.getChildsCount() == 1)
84 {
85 if (lastChild.isInWorkingBranch())
86 {
87 // Reset soft to the first commit to squash
88 QScopedPointer<GitLocal> gitLocal(new GitLocal(mGit));
89 gitLocal->resetCommit(mShas.constFirst(), GitLocal::CommitResetType::SOFT);
90 gitLocal->ammend(msg);
91 }
92 else
93 {
94 QScopedPointer<GitBranches> gitBranches(new GitBranches(mGit));
95
96 // Create auxiliar branch for rebase
97 const auto auxBranch1 = QUuid::createUuid().toString();
98 const auto commitOfAuxBranch1 = lastChild.getFirstChildSha();
99 gitBranches->createBranchAtCommit(commitOfAuxBranch1, auxBranch1);
100
101 // Create auxiliar branch for merge squash
102 const auto auxBranch2 = QUuid::createUuid().toString();
103 gitBranches->createBranchAtCommit(mShas.last(), auxBranch2);
104
105 // Create auxiliar branch for final rebase
106 const auto auxBranch3 = QUuid::createUuid().toString();
107 const auto lastCommit = mCache->commitInfo(CommitInfo::ZERO_SHA).firstParent();
108 gitBranches->createBranchAtCommit(lastCommit, auxBranch3);
109
110 // Reset hard to the first commit to squash
111 QScopedPointer<GitLocal> gitLocal(new GitLocal(mGit));
112 gitLocal->resetCommit(mShas.constFirst(), GitLocal::CommitResetType::HARD);
113
114 // Merge squash auxiliar branch 2
115 QScopedPointer<GitMerge> gitMerge(new GitMerge(mGit, mCache));
116 const auto ret = gitMerge->squashMerge(mGit->getCurrentBranch(), { auxBranch2 }, msg);
117
118 gitBranches->removeLocalBranch(auxBranch2);
119
120 // Rebase auxiliar branch 1
121 const auto destBranch = mGit->getCurrentBranch();
122 gitLocal->cherryPickCommit(commitOfAuxBranch1);
123 gitBranches->rebaseOnto(destBranch, auxBranch1, auxBranch3);
124 gitBranches->removeLocalBranch(auxBranch1);
125 gitBranches->checkoutLocalBranch(destBranch);
126 gitMerge->merge(destBranch, { auxBranch3 });
127 gitBranches->removeLocalBranch(auxBranch3);
128 }
129 }
130
131 QApplication::restoreOverrideCursor();
132
133 emit changesCommitted();
134
135 ui->leCommitTitle->clear();
136 ui->teDescription->clear();
137
138 QDialog::accept();
139 }
140}
141
142void SquashDlg::updateCounter(const QString &text)
143{
144 ui->lCounter->setText(QString::number(mTitleMaxLength - text.count()));
145}
146
147bool SquashDlg::checkMsg(QString &msg)
148{
149 const auto title = ui->leCommitTitle->text();
150
151 if (title.isEmpty())
152 {
153 QMessageBox::warning(this, "Commit changes", "Please, add a title.");
154 return false;
155 }
156
157 msg = title;
158
159 if (!ui->teDescription->toPlainText().isEmpty())
160 {
161 auto description = QString("\n\n%1").arg(ui->teDescription->toPlainText());
162 description.remove(QRegExp("(^|\\n)\\s*#[^\\n]*")); // strip comments
163 msg += description;
164 }
165
166 msg.replace(QRegExp("[ \\t\\r\\f\\v]+\\n"), "\n"); // strip line trailing cruft
167 msg = msg.trimmed();
168
169 if (msg.isEmpty())
170 {
171 QMessageBox::warning(this, "Commit changes", "Please, add a title.");
172 return false;
173 }
174
175 QString subj(msg.section('\n', 0, 0, QString::SectionIncludeTrailingSep));
176 QString body(msg.section('\n', 1).trimmed());
177 msg = subj + '\n' + body + '\n';
178
179 return true;
180}
181