From 2769847e6c39f7694e6deb89dcc7bbd60f261677 Mon Sep 17 00:00:00 2001 From: Micha WERLE Date: Thu, 17 Aug 2023 15:58:30 +0900 Subject: [PATCH 01/12] feat: add a 'Rename Branch' to the right-click context menu in the commit list. --- src/dialogs/CMakeLists.txt | 1 + src/dialogs/RenameBranchDialog.cpp | 60 ++++++++++++++++++++++++++++++ src/dialogs/RenameBranchDialog.h | 37 ++++++++++++++++++ src/ui/CommitList.cpp | 8 ++++ src/ui/RepoView.cpp | 8 ++++ src/ui/RepoView.h | 1 + 6 files changed, 115 insertions(+) create mode 100644 src/dialogs/RenameBranchDialog.cpp create mode 100644 src/dialogs/RenameBranchDialog.h diff --git a/src/dialogs/CMakeLists.txt b/src/dialogs/CMakeLists.txt index bcb82b873..0295257e2 100644 --- a/src/dialogs/CMakeLists.txt +++ b/src/dialogs/CMakeLists.txt @@ -24,6 +24,7 @@ add_library( RebaseConflictDialog.cpp RemoteDialog.cpp RemoteTableModel.cpp + RenameBranchDialog.cpp SettingsDialog.cpp StartDialog.cpp SubmoduleDelegate.cpp diff --git a/src/dialogs/RenameBranchDialog.cpp b/src/dialogs/RenameBranchDialog.cpp new file mode 100644 index 000000000..9531542cf --- /dev/null +++ b/src/dialogs/RenameBranchDialog.cpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2023, Scientific Toolworks, Inc. +// +// This software is licensed under the MIT License. The LICENSE.md file +// describes the conditions under which this software may be distributed. +// +// Author: Michael WERLE +// + +#include "RenameBranchDialog.h" +#include "git/Branch.h" +#include "ui/ExpandButton.h" +#include "ui/ReferenceList.h" +#include "ui/RepoView.h" +#include +#include +#include +#include +#include +#include +#include + +RenameBranchDialog::RenameBranchDialog(const git::Repository &repo, + const git::Branch &branch, + QWidget *parent) + : QDialog(parent) { + Q_ASSERT(branch.isValid() && branch.isLocalBranch()); + setAttribute(Qt::WA_DeleteOnClose); + + mName = new QLineEdit(branch.name(), this); + + QFormLayout *form = new QFormLayout; + form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + form->addRow(tr("Name:"), mName); + + QDialogButtonBox *buttons = new QDialogButtonBox(this); + buttons->addButton(QDialogButtonBox::Cancel); + QPushButton *rename = + buttons->addButton(tr("Rename Branch"), QDialogButtonBox::AcceptRole); + rename->setEnabled(false); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addLayout(form); + layout->addWidget(buttons); + + // Update button when name text changes. + connect(mName, &QLineEdit::textChanged, [repo, rename](const QString &text) { + rename->setEnabled(git::Branch::isNameValid(text) && + !repo.lookupBranch(text, GIT_BRANCH_LOCAL).isValid()); + }); + + // Perform the rename when the button is clicked + connect(rename, &QPushButton::clicked, [this, branch] { + git::Branch(branch).rename(mName->text()); + }); +} + +QString RenameBranchDialog::name() const { return mName->text(); } diff --git a/src/dialogs/RenameBranchDialog.h b/src/dialogs/RenameBranchDialog.h new file mode 100644 index 000000000..82b7b174f --- /dev/null +++ b/src/dialogs/RenameBranchDialog.h @@ -0,0 +1,37 @@ +// +// Copyright (c) 2016, Scientific Toolworks, Inc. +// +// This software is licensed under the MIT License. The LICENSE.md file +// describes the conditions under which this software may be distributed. +// +// Author: Jason Haslam +// + +#ifndef RENAMEBRANCHDIALOG_H +#define RENAMEBRANCHDIALOG_H + +#include "git/Branch.h" +#include + +class QLineEdit; + +namespace git { +class Reference; +class Repository; +} // namespace git + +class RenameBranchDialog : public QDialog { + Q_OBJECT + +public: + RenameBranchDialog(const git::Repository &repo, + const git::Branch &branch, + QWidget *parent = nullptr); + + QString name() const; + +private: + QLineEdit *mName; +}; + +#endif diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index 23dc1067d..45bd2fec4 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1506,6 +1506,14 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addAction(tr("Delete Tag %1").arg(ref.name()), [view, ref] { view->promptToDeleteTag(ref); }); } + if (ref.isLocalBranch()) { + if (separator) { + menu.addSeparator(); + separator = false; + } + menu.addAction(tr("Rename Branch %1").arg(ref.name()), + [view, ref] { view->promptToRenameBranch(ref); }); + } if (ref.isLocalBranch() && (view->repo().head().name() != ref.name())) { if (separator) { menu.addSeparator(); diff --git a/src/ui/RepoView.cpp b/src/ui/RepoView.cpp index d7f9223ae..1c850d015 100644 --- a/src/ui/RepoView.cpp +++ b/src/ui/RepoView.cpp @@ -33,6 +33,7 @@ #include "dialogs/NewBranchDialog.h" #include "dialogs/RebaseConflictDialog.h" #include "dialogs/RemoteDialog.h" +#include "dialogs/RenameBranchDialog.h" #include "dialogs/SettingsDialog.h" #include "dialogs/TagDialog.h" #include "editor/TextEditor.h" @@ -2051,6 +2052,13 @@ void RepoView::promptToDeleteBranch(const git::Reference &ref) { dialog->open(); } +void RepoView::promptToRenameBranch(const git::Branch &branch) { + Q_ASSERT(branch.isValid() && branch.isLocalBranch()); + RenameBranchDialog *dialog = new RenameBranchDialog(mRepo, branch, this); + // The dialog contains the code which performs the rename + dialog->open(); +} + void RepoView::promptToStash() { // Prompt to edit stash commit message. if (!Settings::instance()->prompt(Prompt::Kind::Stash)) { diff --git a/src/ui/RepoView.h b/src/ui/RepoView.h index d045d85d4..fd9cbf2c9 100644 --- a/src/ui/RepoView.h +++ b/src/ui/RepoView.h @@ -251,6 +251,7 @@ class RepoView : public QSplitter { const git::Branch &upstream = git::Branch(), bool checkout = false, bool force = false); void promptToDeleteBranch(const git::Reference &ref); + void promptToRenameBranch(const git::Branch &branch); // stash void promptToStash(); From 5f5d5e9f34f14aa7a40cb01daf43dbd414f31906 Mon Sep 17 00:00:00 2001 From: Micha WERLE Date: Thu, 17 Aug 2023 17:11:01 +0900 Subject: [PATCH 02/12] feat: improve right-click menu for operations on references. Instead of having a big list of references which can be deleted or renamed in the main popup menu we add a sub-menu for each kind of operation if there is more than one reference for an operation. If there is only a single reference for an operation, it is added directly to the menu as per the previous implementation. There's a pro/con here: * pro: best use of menu with easiest operation in the most common scenario (a single reference). * con: menu dynamically changes depending on the data, which means users can't learn muscle-memory. For that it's best to maintain a constant top-level menu and enable/disable menu items as required. This might also make the code a little cleaner. --- src/ui/CommitList.cpp | 75 ++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index 45bd2fec4..41ab3222a 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1496,34 +1496,65 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addAction(tr("New Branch..."), [view, commit] { view->promptToCreateBranch(commit); }); - bool separator = true; - foreach (const git::Reference &ref, commit.refs()) { - if (ref.isTag()) { - if (separator) { - menu.addSeparator(); - separator = false; - } - menu.addAction(tr("Delete Tag %1").arg(ref.name()), - [view, ref] { view->promptToDeleteTag(ref); }); + // Operations on existing references; there may be 0, 1, or multiple of + // each type of reference on a commit. + QList rename_branches; + QList tags; + QList delete_branches; + for(const git::Reference &ref : commit.refs()) { + if(ref.isTag()) { + tags.append(ref); + continue; } - if (ref.isLocalBranch()) { - if (separator) { - menu.addSeparator(); - separator = false; + if(ref.isLocalBranch()) { + rename_branches.append(ref); + if(view->repo().head().name() != ref.name()) { + delete_branches.append(ref); } - menu.addAction(tr("Rename Branch %1").arg(ref.name()), - [view, ref] { view->promptToRenameBranch(ref); }); } - if (ref.isLocalBranch() && (view->repo().head().name() != ref.name())) { - if (separator) { - menu.addSeparator(); - separator = false; - } - menu.addAction(tr("Delete Branch %1").arg(ref.name()), - [view, ref] { view->promptToDeleteBranch(ref); }); + } + + if(rename_branches.count() > 0 || delete_branches.count() > 0 || tags.count() > 0) { + menu.addSeparator(); + } + // rename branches + if(rename_branches.count() > 1) { + auto renameMenu = menu.addMenu(tr("Rename Branch")); + for(const git::Reference &ref : rename_branches) { + renameMenu->addAction(ref.name(), [view, ref] { + view->promptToRenameBranch(ref); }); + } + } else if(rename_branches.count() > 0) { + const git::Reference &ref(rename_branches.first()); + menu.addAction(tr("Rename Branch %1").arg(ref.name()), + [view, ref] { view->promptToRenameBranch(ref); }); + } + + // Delete branches + if(delete_branches.count() > 1) { + auto deleteMenu = menu.addMenu(tr("Delete Branch")); + for(const git::Reference &ref : delete_branches) { + deleteMenu->addAction(ref.name(), [view, ref] { + view->promptToDeleteBranch(ref); }); } + } else if(delete_branches.count() > 0) { + const git::Reference &ref(rename_branches.first()); + menu.addAction(tr("Delete Branch %1").arg(ref.name()), + [view, ref] { view->promptToDeleteBranch(ref); }); } + // Delete tags + if(tags.count() > 1) { + auto deleteMenu = menu.addMenu(tr("Delete Tag")); + for(const git::Reference &ref : tags) { + deleteMenu->addAction(ref.name(), [view, ref] { + view->promptToDeleteTag(ref); }); + } + } else if(tags.count() > 0) { + const git::Reference &ref(tags.first()); + menu.addAction(tr("Delete Tag %1").arg(ref.name()), + [view, ref] { view->promptToDeleteTag(ref); }); + } menu.addSeparator(); menu.addAction(tr("Merge..."), [view, commit] { From 86dd4cb0dba6e16d79698ed09216100472c309f5 Mon Sep 17 00:00:00 2001 From: Micha WERLE Date: Thu, 17 Aug 2023 17:35:49 +0900 Subject: [PATCH 03/12] feat: add rename branch to menu bar --- src/ui/MenuBar.cpp | 9 +++++++++ src/ui/MenuBar.h | 1 + 2 files changed, 10 insertions(+) diff --git a/src/ui/MenuBar.cpp b/src/ui/MenuBar.cpp index 4f14cec16..e06b7c6a8 100644 --- a/src/ui/MenuBar.cpp +++ b/src/ui/MenuBar.cpp @@ -192,6 +192,9 @@ static Hotkey configureBranchesHotkey = HotkeyManager::registerHotkey( static Hotkey newBranchHotkey = HotkeyManager::registerHotkey(nullptr, "branch/new", "Branch/New"); +static Hotkey renameBranchHotkey = + HotkeyManager::registerHotkey(nullptr, "branch/rename", "Branch/Rename"); + static Hotkey checkoutCurrentHotkey = HotkeyManager::registerHotkey( "Ctrl+Shift+Alt+H", "branch/checkoutCurrent", "Branch/Checkout Current"); @@ -640,6 +643,11 @@ MenuBar::MenuBar(QWidget *parent) : QMenuBar(parent) { connect(mNewBranch, &QAction::triggered, [this] { view()->promptToCreateBranch(); }); + mRenameBranch = branch->addAction(tr("Rename Branch")); + renameBranchHotkey.use(mRenameBranch); + connect(mRenameBranch, &QAction::triggered, [this] { + this->view()->promptToRenameBranch(this->view()->reference()); }); + branch->addSeparator(); mCheckoutCurrent = branch->addAction(tr("Checkout Current")); @@ -1050,6 +1058,7 @@ void MenuBar::updateBranch() { mCheckoutCurrent->setEnabled(ref.isValid() && head.isValid() && ref.qualifiedName() != head.qualifiedName()); mCheckout->setEnabled(head.isValid() && !view->repo().isBare()); + mRenameBranch->setEnabled(ref.isLocalBranch()); mNewBranch->setEnabled(head.isValid()); mMerge->setEnabled(head.isValid()); diff --git a/src/ui/MenuBar.h b/src/ui/MenuBar.h index 516eac261..7e04d898c 100644 --- a/src/ui/MenuBar.h +++ b/src/ui/MenuBar.h @@ -116,6 +116,7 @@ class MenuBar : public QMenuBar { QAction *mNewBranch; QAction *mCheckoutCurrent; QAction *mCheckout; + QAction *mRenameBranch; QAction *mMerge; QAction *mRebase; QAction *mSquash; From 6321d39974942f377a2ba5d66df22b8b8e68fcdb Mon Sep 17 00:00:00 2001 From: Micha WERLE Date: Thu, 17 Aug 2023 17:56:32 +0900 Subject: [PATCH 04/12] feat: give the `Rename Branch` dialog a better size. Ideally I would like the dialog to start with a suitable size based on the current branch name, but not force it to be the minimum. However, I could not find a way to do this. Secondly, it would be nice if the dialog automatically expanded up to some maximum size as the name gets longer, again, there seems to be no easy way to do this. --- src/dialogs/RenameBranchDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dialogs/RenameBranchDialog.cpp b/src/dialogs/RenameBranchDialog.cpp index 9531542cf..e95ba858e 100644 --- a/src/dialogs/RenameBranchDialog.cpp +++ b/src/dialogs/RenameBranchDialog.cpp @@ -28,6 +28,8 @@ RenameBranchDialog::RenameBranchDialog(const git::Repository &repo, setAttribute(Qt::WA_DeleteOnClose); mName = new QLineEdit(branch.name(), this); + mName->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Preferred); + mName->setMinimumWidth(QFontMetrics(mName->font()).averageCharWidth() * 40); QFormLayout *form = new QFormLayout; form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); From baa10ebd7be3f5f38659b5e852f2f10dbdcbe939 Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Fri, 18 Aug 2023 09:12:11 +0900 Subject: [PATCH 05/12] feat: make `Checkout` menu multi-level if there are more branches Similar to `Delete` and `Rename`, create a sub-menu if the list of branches available to checkout is larger. TODO: Perhaps make the collapsible branches an option? --- src/ui/CommitList.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index 41ab3222a..3f9574cc3 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1501,15 +1501,17 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { QList rename_branches; QList tags; QList delete_branches; + QList all_branches; // used later for(const git::Reference &ref : commit.refs()) { if(ref.isTag()) { tags.append(ref); - continue; - } - if(ref.isLocalBranch()) { - rename_branches.append(ref); - if(view->repo().head().name() != ref.name()) { - delete_branches.append(ref); + } else if(ref.isBranch()) { + all_branches.append(ref); + if(ref.isLocalBranch()) { + rename_branches.append(ref); + if(view->repo().head().name() != ref.name()) { + delete_branches.append(ref); + } } } } @@ -1612,10 +1614,15 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addSeparator(); git::Reference head = view->repo().head(); - foreach (const git::Reference &ref, commit.refs()) { + QMenu *checkoutMenu = &menu; + if(all_branches.count() > 1) { + checkoutMenu = menu.addMenu(tr("Checkout")); + } + //foreach (const git::Reference &ref, commit.refs()) { + for( const git::Reference &ref : all_branches) { if (ref.isLocalBranch()) { QAction *checkout = - menu.addAction(tr("Checkout %1").arg(ref.name()), + checkoutMenu->addAction(tr("Checkout %1").arg(ref.name()), [view, ref] { view->checkout(ref); }); checkout->setEnabled(head.isValid() && @@ -1623,7 +1630,7 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { !view->repo().isBare()); } else if (ref.isRemoteBranch()) { QAction *checkout = - menu.addAction(tr("Checkout %1").arg(ref.name()), + checkoutMenu->addAction(tr("Checkout %1").arg(ref.name()), [view, ref] { view->checkout(ref); }); // Calculate local branch name in the same way as checkout() does From c9aaa86005300b242ce2bdfed1e4ff5d344d4aae Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Fri, 18 Aug 2023 09:29:06 +0900 Subject: [PATCH 06/12] refactor: rename submenu entries for `Checkout` and code clean up The `Checkout` submenu entries all had "Checkout" as part of their entry; remove it since it's overly verbose. --- src/ui/CommitList.cpp | 66 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index 3f9574cc3..b12bcd061 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1520,42 +1520,39 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addSeparator(); } // rename branches + QMenu *submenu = &menu; + QString entryName(tr("Rename Branch %1")); if(rename_branches.count() > 1) { - auto renameMenu = menu.addMenu(tr("Rename Branch")); - for(const git::Reference &ref : rename_branches) { - renameMenu->addAction(ref.name(), [view, ref] { - view->promptToRenameBranch(ref); }); - } - } else if(rename_branches.count() > 0) { - const git::Reference &ref(rename_branches.first()); - menu.addAction(tr("Rename Branch %1").arg(ref.name()), - [view, ref] { view->promptToRenameBranch(ref); }); + submenu = menu.addMenu(tr("Rename Branch")); + entryName = QString("%1"); + } + for(const git::Reference &ref : rename_branches) { + submenu->addAction(entryName.arg(ref.name()), + [view, ref] { view->promptToRenameBranch(ref); }); } // Delete branches + submenu = &menu; + entryName = tr("Delete Branch %1"); if(delete_branches.count() > 1) { - auto deleteMenu = menu.addMenu(tr("Delete Branch")); - for(const git::Reference &ref : delete_branches) { - deleteMenu->addAction(ref.name(), [view, ref] { - view->promptToDeleteBranch(ref); }); - } - } else if(delete_branches.count() > 0) { - const git::Reference &ref(rename_branches.first()); - menu.addAction(tr("Delete Branch %1").arg(ref.name()), - [view, ref] { view->promptToDeleteBranch(ref); }); + submenu = menu.addMenu(tr("Delete Branch")); + entryName = QString("%1"); + } + for(const git::Reference &ref : delete_branches) { + submenu->addAction(entryName.arg(ref.name()), + [view, ref] { view->promptToDeleteBranch(ref); }); } // Delete tags + submenu = &menu; + entryName = tr("Delete Tag %1"); if(tags.count() > 1) { - auto deleteMenu = menu.addMenu(tr("Delete Tag")); - for(const git::Reference &ref : tags) { - deleteMenu->addAction(ref.name(), [view, ref] { - view->promptToDeleteTag(ref); }); - } - } else if(tags.count() > 0) { - const git::Reference &ref(tags.first()); - menu.addAction(tr("Delete Tag %1").arg(ref.name()), - [view, ref] { view->promptToDeleteTag(ref); }); + submenu = menu.addMenu(tr("Delete Tag")); + entryName = QString("%1"); + } + for(const git::Reference &ref : tags) { + submenu->addAction(entryName.arg(ref.name()), + [view, ref] { view->promptToDeleteTag(ref); }); } menu.addSeparator(); @@ -1614,24 +1611,25 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addSeparator(); git::Reference head = view->repo().head(); - QMenu *checkoutMenu = &menu; + submenu = &menu; + entryName = tr("Checkout %1"); if(all_branches.count() > 1) { - checkoutMenu = menu.addMenu(tr("Checkout")); + submenu = menu.addMenu(tr("Checkout")); + entryName = QString("%1"); } - //foreach (const git::Reference &ref, commit.refs()) { for( const git::Reference &ref : all_branches) { if (ref.isLocalBranch()) { QAction *checkout = - checkoutMenu->addAction(tr("Checkout %1").arg(ref.name()), - [view, ref] { view->checkout(ref); }); + submenu->addAction(entryName.arg(ref.name()), + [view, ref] { view->checkout(ref); }); checkout->setEnabled(head.isValid() && head.qualifiedName() != ref.qualifiedName() && !view->repo().isBare()); } else if (ref.isRemoteBranch()) { QAction *checkout = - checkoutMenu->addAction(tr("Checkout %1").arg(ref.name()), - [view, ref] { view->checkout(ref); }); + submenu->addAction(entryName.arg(ref.name()), + [view, ref] { view->checkout(ref); }); // Calculate local branch name in the same way as checkout() does QString local = ref.name().section('/', 1); From 7721dd28c3468e8bd115ab79761cf974f024a7b9 Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Fri, 18 Aug 2023 10:14:52 +0900 Subject: [PATCH 07/12] refactor(CommitList): move duplicated code generating menu entries into a function. --- src/ui/CommitList.cpp | 65 +++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index b12bcd061..8ba6deff4 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1426,6 +1426,24 @@ void CommitList::setModel(QAbstractItemModel *model) { restoreSelection(); } +/// @brief Helper function to add a list of items to a menu. +/// A single item is added directly to the menu, whereas multiple items will +/// be added to a sub-menu. +static void addMenuEntries(QMenu &menu, const QString &operation, + const QList& items, + std::functionaction) { + QMenu *submenu = &menu; + QString entryName(operation + " %1"); + if(items.count() > 1) { + submenu = menu.addMenu(operation); + entryName = QString("%1"); + } + for(const git::Reference &ref : items) { + submenu->addAction(entryName.arg(ref.name()), + [action, ref] { action(ref); }); + } +} + void CommitList::contextMenuEvent(QContextMenuEvent *event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) @@ -1496,8 +1514,8 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addAction(tr("New Branch..."), [view, commit] { view->promptToCreateBranch(commit); }); - // Operations on existing references; there may be 0, 1, or multiple of - // each type of reference on a commit. + // Add operations on existing references; there may be 0, 1, or multiple + // of each type of reference on a commit. QList rename_branches; QList tags; QList delete_branches; @@ -1519,41 +1537,14 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { if(rename_branches.count() > 0 || delete_branches.count() > 0 || tags.count() > 0) { menu.addSeparator(); } - // rename branches - QMenu *submenu = &menu; - QString entryName(tr("Rename Branch %1")); - if(rename_branches.count() > 1) { - submenu = menu.addMenu(tr("Rename Branch")); - entryName = QString("%1"); - } - for(const git::Reference &ref : rename_branches) { - submenu->addAction(entryName.arg(ref.name()), - [view, ref] { view->promptToRenameBranch(ref); }); - } + addMenuEntries(menu, tr("Rename Branch"), rename_branches, + std::bind(&RepoView::promptToRenameBranch, view, std::placeholders::_1)); - // Delete branches - submenu = &menu; - entryName = tr("Delete Branch %1"); - if(delete_branches.count() > 1) { - submenu = menu.addMenu(tr("Delete Branch")); - entryName = QString("%1"); - } - for(const git::Reference &ref : delete_branches) { - submenu->addAction(entryName.arg(ref.name()), - [view, ref] { view->promptToDeleteBranch(ref); }); - } + addMenuEntries(menu, tr("Delete Branch"), delete_branches, + std::bind(&RepoView::promptToDeleteBranch, view, std::placeholders::_1)); - // Delete tags - submenu = &menu; - entryName = tr("Delete Tag %1"); - if(tags.count() > 1) { - submenu = menu.addMenu(tr("Delete Tag")); - entryName = QString("%1"); - } - for(const git::Reference &ref : tags) { - submenu->addAction(entryName.arg(ref.name()), - [view, ref] { view->promptToDeleteTag(ref); }); - } + addMenuEntries(menu, tr("Delete Tag"), tags, + std::bind(&RepoView::promptToDeleteTag, view, std::placeholders::_1)); menu.addSeparator(); menu.addAction(tr("Merge..."), [view, commit] { @@ -1611,8 +1602,8 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { menu.addSeparator(); git::Reference head = view->repo().head(); - submenu = &menu; - entryName = tr("Checkout %1"); + auto submenu = &menu; + auto entryName = tr("Checkout %1"); if(all_branches.count() > 1) { submenu = menu.addMenu(tr("Checkout")); entryName = QString("%1"); From 8745c3c5c9ef818da22e3e8556ebd07695af0f79 Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Fri, 18 Aug 2023 10:17:01 +0900 Subject: [PATCH 08/12] chore: fix copyright information in the `RenameBranchDialog` file comments Fix the copyright information in the files authored by myself. --- src/dialogs/RenameBranchDialog.cpp | 3 --- src/dialogs/RenameBranchDialog.h | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/dialogs/RenameBranchDialog.cpp b/src/dialogs/RenameBranchDialog.cpp index e95ba858e..110db221b 100644 --- a/src/dialogs/RenameBranchDialog.cpp +++ b/src/dialogs/RenameBranchDialog.cpp @@ -1,6 +1,3 @@ -// -// Copyright (c) 2023, Scientific Toolworks, Inc. -// // This software is licensed under the MIT License. The LICENSE.md file // describes the conditions under which this software may be distributed. // diff --git a/src/dialogs/RenameBranchDialog.h b/src/dialogs/RenameBranchDialog.h index 82b7b174f..fb81d6fda 100644 --- a/src/dialogs/RenameBranchDialog.h +++ b/src/dialogs/RenameBranchDialog.h @@ -1,10 +1,7 @@ -// -// Copyright (c) 2016, Scientific Toolworks, Inc. -// // This software is licensed under the MIT License. The LICENSE.md file // describes the conditions under which this software may be distributed. // -// Author: Jason Haslam +// Author: Michael WERLE // #ifndef RENAMEBRANCHDIALOG_H From 42baad6dfe14213de33d1c860c11798c5a4f59d8 Mon Sep 17 00:00:00 2001 From: Micha WERLE Date: Wed, 23 Aug 2023 08:03:19 +0900 Subject: [PATCH 09/12] chore: clang-format recently modified files. TODO: add clang-format as a pre-commit and/or pre-push git hook --- src/dialogs/RenameBranchDialog.cpp | 7 ++--- src/dialogs/RenameBranchDialog.h | 5 ++-- src/ui/CommitList.cpp | 46 ++++++++++++++++-------------- src/ui/MenuBar.cpp | 3 +- test/Setting.cpp | 4 ++- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/dialogs/RenameBranchDialog.cpp b/src/dialogs/RenameBranchDialog.cpp index 110db221b..b7580ed4f 100644 --- a/src/dialogs/RenameBranchDialog.cpp +++ b/src/dialogs/RenameBranchDialog.cpp @@ -25,7 +25,7 @@ RenameBranchDialog::RenameBranchDialog(const git::Repository &repo, setAttribute(Qt::WA_DeleteOnClose); mName = new QLineEdit(branch.name(), this); - mName->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Preferred); + mName->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); mName->setMinimumWidth(QFontMetrics(mName->font()).averageCharWidth() * 40); QFormLayout *form = new QFormLayout; @@ -51,9 +51,8 @@ RenameBranchDialog::RenameBranchDialog(const git::Repository &repo, }); // Perform the rename when the button is clicked - connect(rename, &QPushButton::clicked, [this, branch] { - git::Branch(branch).rename(mName->text()); - }); + connect(rename, &QPushButton::clicked, + [this, branch] { git::Branch(branch).rename(mName->text()); }); } QString RenameBranchDialog::name() const { return mName->text(); } diff --git a/src/dialogs/RenameBranchDialog.h b/src/dialogs/RenameBranchDialog.h index fb81d6fda..5aa4538c8 100644 --- a/src/dialogs/RenameBranchDialog.h +++ b/src/dialogs/RenameBranchDialog.h @@ -21,9 +21,8 @@ class RenameBranchDialog : public QDialog { Q_OBJECT public: - RenameBranchDialog(const git::Repository &repo, - const git::Branch &branch, - QWidget *parent = nullptr); + RenameBranchDialog(const git::Repository &repo, const git::Branch &branch, + QWidget *parent = nullptr); QString name() const; diff --git a/src/ui/CommitList.cpp b/src/ui/CommitList.cpp index 8ba6deff4..c3cb132b7 100644 --- a/src/ui/CommitList.cpp +++ b/src/ui/CommitList.cpp @@ -1430,15 +1430,15 @@ void CommitList::setModel(QAbstractItemModel *model) { /// A single item is added directly to the menu, whereas multiple items will /// be added to a sub-menu. static void addMenuEntries(QMenu &menu, const QString &operation, - const QList& items, - std::functionaction) { + const QList &items, + std::function action) { QMenu *submenu = &menu; QString entryName(operation + " %1"); - if(items.count() > 1) { + if (items.count() > 1) { submenu = menu.addMenu(operation); entryName = QString("%1"); } - for(const git::Reference &ref : items) { + for (const git::Reference &ref : items) { submenu->addAction(entryName.arg(ref.name()), [action, ref] { action(ref); }); } @@ -1520,31 +1520,35 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { QList tags; QList delete_branches; QList all_branches; // used later - for(const git::Reference &ref : commit.refs()) { - if(ref.isTag()) { + for (const git::Reference &ref : commit.refs()) { + if (ref.isTag()) { tags.append(ref); - } else if(ref.isBranch()) { + } else if (ref.isBranch()) { all_branches.append(ref); - if(ref.isLocalBranch()) { + if (ref.isLocalBranch()) { rename_branches.append(ref); - if(view->repo().head().name() != ref.name()) { + if (view->repo().head().name() != ref.name()) { delete_branches.append(ref); } } } } - if(rename_branches.count() > 0 || delete_branches.count() > 0 || tags.count() > 0) { + if (rename_branches.count() > 0 || delete_branches.count() > 0 || + tags.count() > 0) { menu.addSeparator(); } addMenuEntries(menu, tr("Rename Branch"), rename_branches, - std::bind(&RepoView::promptToRenameBranch, view, std::placeholders::_1)); + std::bind(&RepoView::promptToRenameBranch, view, + std::placeholders::_1)); addMenuEntries(menu, tr("Delete Branch"), delete_branches, - std::bind(&RepoView::promptToDeleteBranch, view, std::placeholders::_1)); + std::bind(&RepoView::promptToDeleteBranch, view, + std::placeholders::_1)); - addMenuEntries(menu, tr("Delete Tag"), tags, - std::bind(&RepoView::promptToDeleteTag, view, std::placeholders::_1)); + addMenuEntries( + menu, tr("Delete Tag"), tags, + std::bind(&RepoView::promptToDeleteTag, view, std::placeholders::_1)); menu.addSeparator(); menu.addAction(tr("Merge..."), [view, commit] { @@ -1604,23 +1608,21 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) { git::Reference head = view->repo().head(); auto submenu = &menu; auto entryName = tr("Checkout %1"); - if(all_branches.count() > 1) { + if (all_branches.count() > 1) { submenu = menu.addMenu(tr("Checkout")); entryName = QString("%1"); } - for( const git::Reference &ref : all_branches) { + for (const git::Reference &ref : all_branches) { if (ref.isLocalBranch()) { - QAction *checkout = - submenu->addAction(entryName.arg(ref.name()), - [view, ref] { view->checkout(ref); }); + QAction *checkout = submenu->addAction( + entryName.arg(ref.name()), [view, ref] { view->checkout(ref); }); checkout->setEnabled(head.isValid() && head.qualifiedName() != ref.qualifiedName() && !view->repo().isBare()); } else if (ref.isRemoteBranch()) { - QAction *checkout = - submenu->addAction(entryName.arg(ref.name()), - [view, ref] { view->checkout(ref); }); + QAction *checkout = submenu->addAction( + entryName.arg(ref.name()), [view, ref] { view->checkout(ref); }); // Calculate local branch name in the same way as checkout() does QString local = ref.name().section('/', 1); diff --git a/src/ui/MenuBar.cpp b/src/ui/MenuBar.cpp index e06b7c6a8..9c7549006 100644 --- a/src/ui/MenuBar.cpp +++ b/src/ui/MenuBar.cpp @@ -646,7 +646,8 @@ MenuBar::MenuBar(QWidget *parent) : QMenuBar(parent) { mRenameBranch = branch->addAction(tr("Rename Branch")); renameBranchHotkey.use(mRenameBranch); connect(mRenameBranch, &QAction::triggered, [this] { - this->view()->promptToRenameBranch(this->view()->reference()); }); + this->view()->promptToRenameBranch(this->view()->reference()); + }); branch->addSeparator(); diff --git a/test/Setting.cpp b/test/Setting.cpp index 0a146bbad..125b0cb2b 100644 --- a/test/Setting.cpp +++ b/test/Setting.cpp @@ -23,7 +23,9 @@ private slots: template QStringList settingsKeys() { QStringList settingsKeys; - foreach (const TId id, ids()) { settingsKeys.append(T::key(id)); } + foreach (const TId id, ids()) { + settingsKeys.append(T::key(id)); + } return settingsKeys; } From 6185b8442d46a86eae95125f04b82d3afa9c1c5a Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Thu, 24 Aug 2023 07:38:20 +0900 Subject: [PATCH 10/12] build: update `cl-format.sh` to force v13 of clang-format The script was designed to find the latest version of clang-format, however, this project currently specifically requires the use of v13. We now only check for v13 of clang-format. Secondly, for cmake-format the script also requires a specific version. Instead of installing that unmanaged version globally we now install it inside a python venv. --- .gitignore | 2 ++ cl-fmt.sh | 33 +++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index edbbfdfca..6786e6c10 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ build CMakeLists.txt.user cmake-build-debug/ cmake-build-release/ +build .idea/ +.venv diff --git a/cl-fmt.sh b/cl-fmt.sh index 05922b135..2243194eb 100755 --- a/cl-fmt.sh +++ b/cl-fmt.sh @@ -1,5 +1,6 @@ #!/bin/bash +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "`dirname "$0"`" # Variable that will hold the name of the clang-format command @@ -7,12 +8,11 @@ FMT="" FOLDERS=("./src" "./test") -# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent -# that the version number be part of the command. We prefer clang-format if -# that's present, otherwise we work backwards from highest version to lowest -# version but at least 13. -for clangfmt in clang-format{,-{1,2,3}{9,8,7,6,5,4,3}}; do - if which "$clangfmt" &>/dev/null; then +# We specifically require clang-format v13. Some distros include the version +# number in the name, others don't. Prefer the specifically-named version. +for clangfmt in clang-format-13 clang-format +do + if command -v "$clangfmt" &>/dev/null; then FMT="$clangfmt" break fi @@ -24,6 +24,14 @@ if [ -z "$FMT" ]; then exit 1 fi +# Check we have v13 of clang-format +VERSION=`$FMT --version | grep -Po 'version\s\K(\d+)'` +if [ "$VERSION" != "13" ]; then + echo "Found clang-format v$VERSION, but v13 is required. Please install v13 of clang-format and try again." + echo "On Debian-derived distributions, this can be done via: apt install clang-format-13" + exit 1 +fi + function format() { for f in $(find $@ \( -type d -path './test/dep/*' -prune \) -o \( -name '*.h' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \)); do echo "format ${f}"; @@ -41,9 +49,18 @@ for dir in ${FOLDERS[@]}; do fi done +# Format cmake files +# NOTE: requires support for python venv; on Debian-like distros, this can be +# installed using apt install python3-venv echo "Start formatting cmake files" -pip install cmake-format==0.6.13 +CMAKE_FORMAT=${SCRIPT_DIR}/.venv/bin/cmake-format +if [ ! -f "$CMAKE_FORMAT" ]; then + pushd ${SCRIPT_DIR} + python3 -m venv .venv + .venv/bin/pip install cmake-format==0.6.13 + popd +fi find . \ \( -type d -path './test/dep/*' -prune \) \ -o \( -type d -path './dep/*/*' -prune \) \ - -o \( -name CMakeLists.txt -exec cmake-format --in-place {} + \) + -o \( -name CMakeLists.txt -exec "$CMAKE_FORMAT" --in-place {} + \) From f71f73c3b7b185ab3831d7af53b5e318e5fd9e62 Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Tue, 29 Aug 2023 08:25:56 +0900 Subject: [PATCH 11/12] chore: source code formatting Revert incorrect formatting changes due to use of clang-format v14 in 6ed70b2745c4919e047d4731f8573fc573bb15c5. --- test/Setting.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/Setting.cpp b/test/Setting.cpp index 125b0cb2b..0a146bbad 100644 --- a/test/Setting.cpp +++ b/test/Setting.cpp @@ -23,9 +23,7 @@ private slots: template QStringList settingsKeys() { QStringList settingsKeys; - foreach (const TId id, ids()) { - settingsKeys.append(T::key(id)); - } + foreach (const TId id, ids()) { settingsKeys.append(T::key(id)); } return settingsKeys; } From 4ef8b60aca7948465bc7f37bec8d8c61bb277830 Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Tue, 29 Aug 2023 08:44:32 +0900 Subject: [PATCH 12/12] docs: update changelog with rename-branch work. --- docs/changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index bd984193b..567609a77 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,8 +4,15 @@ Description #### Added +* UI(Commit List): Added a right-click menu entry to rename branches. +* UI(Main Menu): Added a menu-entry to rename the current branch. + #### Changed +* UI(Commit List): Collapse multiple branch and tag right-click menu entries + into submenus. This affects the checkout and delete operations. +* Fix(Build System): Force usage of clang-format v13 to ensure consistent formatting. + ---- ### v1.3.0 - 2023-04-20