Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/rename branch menu #621

Merged
merged 12 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ build
CMakeLists.txt.user
cmake-build-debug/
cmake-build-release/
build
.idea/
.venv
33 changes: 25 additions & 8 deletions cl-fmt.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/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
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
Expand All @@ -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}";
Expand All @@ -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 {} + \)
7 changes: 7 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/dialogs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_library(
RebaseConflictDialog.cpp
RemoteDialog.cpp
RemoteTableModel.cpp
RenameBranchDialog.cpp
SettingsDialog.cpp
StartDialog.cpp
SubmoduleDelegate.cpp
Expand Down
58 changes: 58 additions & 0 deletions src/dialogs/RenameBranchDialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 <QApplication>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>

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);
mName->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
mName->setMinimumWidth(QFontMetrics(mName->font()).averageCharWidth() * 40);

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(); }
33 changes: 33 additions & 0 deletions src/dialogs/RenameBranchDialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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
//

#ifndef RENAMEBRANCHDIALOG_H
#define RENAMEBRANCHDIALOG_H

#include "git/Branch.h"
#include <QDialog>

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
81 changes: 59 additions & 22 deletions src/ui/CommitList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<git::Reference> &items,
std::function<void(const git::Reference &)> action) {
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())
Expand Down Expand Up @@ -1496,26 +1514,41 @@ 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()) {
// Add operations on existing references; there may be 0, 1, or multiple
// of each type of reference on a commit.
QList<git::Reference> rename_branches;
QList<git::Reference> tags;
QList<git::Reference> delete_branches;
QList<git::Reference> all_branches; // used later
for (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); });
}
if (ref.isLocalBranch() && (view->repo().head().name() != ref.name())) {
if (separator) {
menu.addSeparator();
separator = false;
tags.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);
}
}
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();
}
addMenuEntries(menu, tr("Rename Branch"), rename_branches,
std::bind(&RepoView::promptToRenameBranch, view,
std::placeholders::_1));

addMenuEntries(menu, tr("Delete Branch"), delete_branches,
std::bind(&RepoView::promptToDeleteBranch, 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] {
Expand Down Expand Up @@ -1573,19 +1606,23 @@ void CommitList::contextMenuEvent(QContextMenuEvent *event) {
menu.addSeparator();

git::Reference head = view->repo().head();
foreach (const git::Reference &ref, commit.refs()) {
auto submenu = &menu;
auto entryName = tr("Checkout %1");
if (all_branches.count() > 1) {
submenu = menu.addMenu(tr("Checkout"));
entryName = QString("%1");
}
for (const git::Reference &ref : all_branches) {
Murmele marked this conversation as resolved.
Show resolved Hide resolved
if (ref.isLocalBranch()) {
QAction *checkout =
menu.addAction(tr("Checkout %1").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 =
menu.addAction(tr("Checkout %1").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);
Expand Down
10 changes: 10 additions & 0 deletions src/ui/MenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -640,6 +643,12 @@ 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"));
Expand Down Expand Up @@ -1050,6 +1059,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());
Expand Down
1 change: 1 addition & 0 deletions src/ui/MenuBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class MenuBar : public QMenuBar {
QAction *mNewBranch;
QAction *mCheckoutCurrent;
QAction *mCheckout;
QAction *mRenameBranch;
QAction *mMerge;
QAction *mRebase;
QAction *mSquash;
Expand Down
8 changes: 8 additions & 0 deletions src/ui/RepoView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions src/ui/RepoView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading