From 6e27905bd6aaa79b2a3a58148181fd8ace8bd5d3 Mon Sep 17 00:00:00 2001 From: Esteban Aliverti Date: Thu, 1 Aug 2024 16:38:44 +0200 Subject: [PATCH] Add support for ${author} and ${branch} in templates --- src/ui/CommitEditor.cpp | 120 +++++++++++++++++++++++++++++--------- src/ui/CommitEditor.h | 13 ++++- src/ui/TemplateButton.cpp | 5 ++ src/ui/TemplateButton.h | 2 + src/ui/TemplateDialog.cpp | 11 ++++ test/commitEditor.cpp | 81 ++++++++++++++++++++++++- 6 files changed, 199 insertions(+), 33 deletions(-) diff --git a/src/ui/CommitEditor.cpp b/src/ui/CommitEditor.cpp index c885c235e..7ad703eca 100644 --- a/src/ui/CommitEditor.cpp +++ b/src/ui/CommitEditor.cpp @@ -277,7 +277,17 @@ CommitEditor::CommitEditor(const git::Repository &repo, QWidget *parent) } } } - applyTemplate(t, files); + + RepoView *view = RepoView::parentView(this); + git::Reference head = view ? view->repo().head() : git::Reference(); + git::Branch headBranch = head; + + QString branchName = git::Branch(head).name(); + git::Config config = + view ? view->repo().gitConfig() : git::Config::global(); + QString author = config.value("user.name").toHtmlEscaped(); + + applyTemplate(t, author, branchName, files); }); QLabel *label = new QLabel(tr("Commit Message:"), this); @@ -605,9 +615,11 @@ QString CommitEditor::createFileList(const QStringList &list, int maxFiles) { return msg; } -void CommitEditor::setMessage(const QStringList &files) { +void CommitEditor::setMessage(const QString author, const QString branchName, + const QStringList &files) { if (mTemplate->templates().count() > 0) { - applyTemplate(mTemplate->templates().first().value, files); + applyTemplate(mTemplate->templates().first().value, author, branchName, + files); } else { QString msg = createFileList(files, 3); if (!msg.isEmpty()) @@ -634,49 +646,97 @@ void CommitEditor::setDiff(const git::Diff &diff) { } // public slots -void CommitEditor::applyTemplate(const QString &t, const QStringList &files) { +void CommitEditor::applyTemplate(const QString &t, const QString &author, + const QString &branchName, + const QStringList &files) { QString templ = t; + templ = applyAuthorTemplate(templ, author); + templ = applyBranchTemplate(templ, branchName); + templ = applyFileTemplate(templ, files); + + auto index = templ.indexOf(TemplateButton::cursorPositionString); + if (index < 0) { + index = templ.length(); + } + + templ.replace(TemplateButton::cursorPositionString, ""); + mMessage->setText(templ); + auto cursor = mMessage->textCursor(); + cursor.setPosition(index); + mMessage->setTextCursor(cursor); +} + +void CommitEditor::applyTemplate(const QString &t, const QString &author, + const QString &branchName) { + applyTemplate(t, author, branchName, {}); +} + +QString CommitEditor::applyAuthorTemplate(const QString &t, + const QString author) { + QString templateText = t; + //${author} + QString pattern = TemplateButton::authorPattern; + pattern.replace("{", "\\{"); + pattern.replace("}", "\\}"); + pattern.replace("$", "\\$"); + QRegularExpression re(pattern); + QRegularExpressionMatch match = re.match(templateText); + if (match.hasMatch()) { + const auto matchComplete = match.captured(0); + templateText.replace(matchComplete, author); + } + return templateText; +} +QString CommitEditor::applyBranchTemplate(const QString &t, + const QString branchName) { + QString templateText = t; + //${branch(:.*){0,1}} + QString pattern = TemplateButton::branchPattern; + pattern = pattern.replace(QRegularExpression("^\\$\\{"), "\\$\\{"); + pattern = pattern.replace(QRegularExpression("\\}$"), "\\}"); + QRegularExpression re(pattern); + QRegularExpressionMatch match = re.match(templateText); + if (match.hasMatch()) { + const auto matchComplete = match.captured(0); + QString branchPart = branchName; + int idx = matchComplete.indexOf(':'); + if (idx > 0) { + QRegularExpression branchRegex( + "(" + matchComplete.mid(idx + 1, matchComplete.length() - idx - 2) + + ")"); + match = branchRegex.match(branchName); + branchPart = match.captured(1); + } + templateText.replace(matchComplete, branchPart); + } + return templateText; +} + +QString CommitEditor::applyFileTemplate(const QString &t, + const QStringList &files) { + QString templateText = t; + //${file:} QString pattern = TemplateButton::filesPosition; pattern.replace("{", "\\{"); pattern.replace("}", "\\}"); pattern.replace("$", "\\$"); QRegularExpression re(pattern); - QRegularExpressionMatch match = re.match(templ); - int start = -1; - int offset = 0; + QRegularExpressionMatch match = re.match(templateText); if (match.hasMatch()) { - start = match.capturedStart(0); - int origLength = match.capturedLength(0); const auto matchComplete = match.captured(0); bool ok; const auto number = match.captured(1).toInt(&ok); if (ok) { const QString filesStr = createFileList(files, number); - templ.replace(matchComplete, filesStr); - offset = filesStr.length() - origLength; + templateText.replace(matchComplete, filesStr); } } - - auto index = t.indexOf(TemplateButton::cursorPositionString); - if (index < 0) - index = templ.length(); - else if (start > 0 && index > start) { - // offset, because fileStr has different length than matchComplete - index += offset; - } - - templ.replace(TemplateButton::cursorPositionString, ""); - mMessage->setText(templ); - auto cursor = mMessage->textCursor(); - cursor.setPosition(index); - mMessage->setTextCursor(cursor); + return templateText; } -void CommitEditor::applyTemplate(const QString &t) { applyTemplate(t, {}); } - void CommitEditor::updateButtons(bool yieldFocus) { RepoView *view = RepoView::parentView(this); if (!view || !view->repo().isValid()) { @@ -720,6 +780,10 @@ void CommitEditor::updateButtons(bool yieldFocus) { git::Reference head = view ? view->repo().head() : git::Reference(); git::Branch headBranch = head; + QString branchName = git::Branch(head).name(); + git::Config config = view ? view->repo().gitConfig() : git::Config::global(); + QString author = config.value("user.name").toHtmlEscaped(); + mMergeAbort->setText(tr("Abort %1").arg(text)); mMergeAbort->setVisible(headBranch.isValid() && merging); @@ -763,7 +827,7 @@ void CommitEditor::updateButtons(bool yieldFocus) { QSignalBlocker blocker(mMessage); (void)blocker; - setMessage(files); + setMessage(author, branchName, files); if (yieldFocus && !mMessage->toPlainText().isEmpty()) mMessage->setFocus(); } diff --git a/src/ui/CommitEditor.h b/src/ui/CommitEditor.h index 3b452f4c8..f34bbe508 100644 --- a/src/ui/CommitEditor.h +++ b/src/ui/CommitEditor.h @@ -35,17 +35,24 @@ class CommitEditor : public QFrame { void unstage(); bool isUnstageEnabled() const; static QString createFileList(const QStringList &list, int maxFiles); - void setMessage(const QStringList &files); + void setMessage(const QString author, const QString branchName, + const QStringList &files); void setMessage(const QString &message); QString message() const; void setDiff(const git::Diff &diff); public slots: - void applyTemplate(const QString &t, const QStringList &files); - void applyTemplate(const QString &t); + void applyTemplate(const QString &t, const QString &author, + const QString &branchName, const QStringList &files); + void applyTemplate(const QString &t, const QString &author, + const QString &branchName); private: void updateButtons(bool yieldFocus = true); + QString applyAuthorTemplate(const QString &t, const QString author); + QString applyBranchTemplate(const QString &t, const QString branchName); + QString applyFileTemplate(const QString &templateText, + const QStringList &files); QTextEdit *textEdit() const; git::Repository mRepo; diff --git a/src/ui/TemplateButton.cpp b/src/ui/TemplateButton.cpp index aebe286ff..f285362f9 100644 --- a/src/ui/TemplateButton.cpp +++ b/src/ui/TemplateButton.cpp @@ -19,6 +19,11 @@ const QString TemplateButton::cursorPositionString = QStringLiteral("%|"); const QString TemplateButton::filesPosition = QStringLiteral("${files:([0-9]*)}"); +const QString TemplateButton::authorPattern = QStringLiteral("${author}"); + +const QString TemplateButton::branchPattern = + QStringLiteral("${branch(:.*){0,1}}"); + TemplateButton::TemplateButton(QWidget *parent) : QToolButton(parent) { setPopupMode(QToolButton::InstantPopup); mMenu = new QMenu(this); diff --git a/src/ui/TemplateButton.h b/src/ui/TemplateButton.h index 44a559836..2ea0685de 100644 --- a/src/ui/TemplateButton.h +++ b/src/ui/TemplateButton.h @@ -14,6 +14,8 @@ class TemplateButton : public QToolButton { }; static const QString cursorPositionString; static const QString filesPosition; + static const QString authorPattern; + static const QString branchPattern; TemplateButton(QWidget *parent = nullptr); QMenu *menu() const; diff --git a/src/ui/TemplateDialog.cpp b/src/ui/TemplateDialog.cpp index 4a1ac7326..16dc10e17 100644 --- a/src/ui/TemplateDialog.cpp +++ b/src/ui/TemplateDialog.cpp @@ -52,6 +52,17 @@ TemplateDialog::TemplateDialog(QList &templates, vBox->addWidget(new QLabel(tr("use %1 to declare the position of the cursor.") .arg(TemplateButton::cursorPositionString), this)); + vBox->addWidget( + new QLabel(tr("use ${author} to add the author of the commit"), this)); + + vBox->addWidget( + new QLabel(tr("use ${branch} to add the name of the branch."), this)); + + vBox->addWidget(new QLabel( + tr("use ${branch:x} to add part of the name of the branch.,\nx (regex)" + "determines the regular expression used to extract the desired part"), + this)); + vBox->addWidget( new QLabel(tr("use ${files:x} to add all updated file names,\nx (number) " "determines the number of maximum files shown"), diff --git a/test/commitEditor.cpp b/test/commitEditor.cpp index db24a7068..075d6add4 100644 --- a/test/commitEditor.cpp +++ b/test/commitEditor.cpp @@ -11,6 +11,11 @@ private slots: void testCreateFileList(); void applyTemplate1(); void applyTemplate2(); + void applyTemplate3(); + void applyTemplate4(); + void applyTemplate5(); + void applyTemplate6(); + void applyTemplate7(); }; void TestCommitEditor::testCreateFileList() { @@ -32,7 +37,7 @@ void TestCommitEditor::applyTemplate1() { Test::ScratchRepository repo; CommitEditor e(repo); - e.applyTemplate(QStringLiteral("Description: %|\nfiles: ${files:3}"), + e.applyTemplate(QStringLiteral("Description: %|\nfiles: ${files:3}"), "", "", {"file.txt"}); QCOMPARE(e.textEdit()->toPlainText(), QStringLiteral("Description: \nfiles: file.txt")); @@ -46,7 +51,7 @@ void TestCommitEditor::applyTemplate2() { // Cursor after inserted files e.applyTemplate( QStringLiteral("Description: \nfiles: ${files:3}\nCursorPosition: %|"), - {"reallylongfilename.txt"}); + "", "", {"reallylongfilename.txt"}); QCOMPARE( e.textEdit()->toPlainText(), QStringLiteral( @@ -54,5 +59,77 @@ void TestCommitEditor::applyTemplate2() { QCOMPARE(e.textEdit()->textCursor().position(), 60); } +void TestCommitEditor::applyTemplate3() { + Test::ScratchRepository repo; + CommitEditor e(repo); + + // author + e.applyTemplate( + QStringLiteral("Description: \nauthor: ${author}\nCursorPosition: %|"), + "john.doe", "", {"file.txt"}); + QCOMPARE(e.textEdit()->toPlainText(), + QStringLiteral("Description: \nauthor: john.doe\nCursorPosition: ")); + QCOMPARE(e.textEdit()->textCursor().position(), 47); +} + +void TestCommitEditor::applyTemplate4() { + Test::ScratchRepository repo; + CommitEditor e(repo); + + // branch + e.applyTemplate( + QStringLiteral("Description: \nbranch: ${branch}\nCursorPosition: %|"), + "john.doe", "bugfix/GTTY-01", {"file.txt"}); + QCOMPARE(e.textEdit()->toPlainText(), + QStringLiteral( + "Description: \nbranch: bugfix/GTTY-01\nCursorPosition: ")); + QCOMPARE(e.textEdit()->textCursor().position(), 53); +} + +void TestCommitEditor::applyTemplate5() { + Test::ScratchRepository repo; + CommitEditor e(repo); + + // branch + e.applyTemplate( + QStringLiteral("Description: \nissue: " + "${branch:(GTTY-[0-9]{2})}\nCursorPosition: %|"), + "john.doe", "bugfix/GTTY-01", {"file.txt"}); + QCOMPARE(e.textEdit()->toPlainText(), + QStringLiteral("Description: \nissue: GTTY-01\nCursorPosition: ")); + QCOMPARE(e.textEdit()->textCursor().position(), 45); +} + +void TestCommitEditor::applyTemplate6() { + Test::ScratchRepository repo; + CommitEditor e(repo); + + // branch (no match) + e.applyTemplate( + QStringLiteral( + "Description: \nissue: ${branch:(XXX-[0-9]{2})}\nCursorPosition: %|"), + "john.doe", "bugfix/GTTY-01", {"file.txt"}); + QCOMPARE(e.textEdit()->toPlainText(), + QStringLiteral("Description: \nissue: \nCursorPosition: ")); + QCOMPARE(e.textEdit()->textCursor().position(), 38); +} + +void TestCommitEditor::applyTemplate7() { + Test::ScratchRepository repo; + CommitEditor e(repo); + + // author, branch and files + e.applyTemplate( + QStringLiteral( + "Description: \nauthor: ${author} \nissue: ${branch:(GTTY-[0-9]{2})} " + "\nfiles: ${files:2} \nCursorPosition: %|"), + "john.doe", "bugfix/GTTY-02", {"file1.txt", "file2.txt", "file3.txt"}); + QCOMPARE(e.textEdit()->toPlainText(), + QStringLiteral( + "Description: \nauthor: john.doe \nissue: GTTY-02 \nfiles: " + "file1.txt, file2.txt, and 1 more file \nCursorPosition: ")); + QCOMPARE(e.textEdit()->textCursor().position(), 110); +} + TEST_MAIN(TestCommitEditor) #include "commitEditor.moc"