Support deleting remote branches via the delete button

The delete button and context menu action now detect whether the
selected branch is remote (tracked via Qt::UserRole on list items).
Remote branches are deleted with `git push --delete <remote> <branch>`
with an appropriate confirmation dialog warning about server-side
removal. Local branches continue using `git branch -D`.
This commit is contained in:
Levi Neely 2026-07-16 05:38:10 +02:00
parent 3642f8b743
commit 7dabd4b9e6
2 changed files with 66 additions and 20 deletions

View File

@ -836,6 +836,7 @@ void GitPlusPlusView::onBranchFinished(int exitCode, QProcess::ExitStatus)
if (exitCode == 0) { if (exitCode == 0) {
QByteArray output = proc->readAllStandardOutput(); QByteArray output = proc->readAllStandardOutput();
m_allBranches.clear(); m_allBranches.clear();
m_remoteBranches.clear();
QString current; QString current;
const auto lines = output.split('\n'); const auto lines = output.split('\n');
@ -849,17 +850,25 @@ void GitPlusPlusView::onBranchFinished(int exitCode, QProcess::ExitStatus)
// Skip HEAD pointer in remotes // Skip HEAD pointer in remotes
if (branch.contains(QStringLiteral(" -> "))) continue; if (branch.contains(QStringLiteral(" -> "))) continue;
// Strip "remotes/" prefix for display (show as origin/foo) // Strip "remotes/" prefix for display (show as origin/foo)
if (branch.startsWith(QStringLiteral("remotes/"))) bool isRemote = false;
if (branch.startsWith(QStringLiteral("remotes/"))) {
branch = branch.mid(8); // len("remotes/") == 8 branch = branch.mid(8); // len("remotes/") == 8
isRemote = true;
}
// Deduplicate: if local branch matches a remote, skip the remote // Deduplicate: if local branch matches a remote, skip the remote
if (!m_allBranches.contains(branch)) if (!m_allBranches.contains(branch)) {
m_allBranches.append(branch); m_allBranches.append(branch);
if (isRemote)
m_remoteBranches.append(branch);
}
} }
// Update branch list widget // Update branch list widget
m_branchList->clear(); m_branchList->clear();
for (const auto &b : m_allBranches) { for (const auto &b : m_allBranches) {
auto *item = new QListWidgetItem(b, m_branchList); auto *item = new QListWidgetItem(b, m_branchList);
// Mark remote branches (those that came from remotes/ prefix)
item->setData(Qt::UserRole, m_remoteBranches.contains(b));
if (b == current) { if (b == current) {
item->setText(QStringLiteral("* ") + b); item->setText(QStringLiteral("* ") + b);
QFont f = item->font(); QFont f = item->font();
@ -1202,26 +1211,62 @@ void GitPlusPlusView::deleteBranch()
return; return;
} }
if (QMessageBox::question(m_toolView, QStringLiteral("Delete Branch"), // Detect if this is a remote branch via the UserRole flag
QStringLiteral("Delete branch '%1'?").arg(branch), auto *item = m_branchList->currentItem();
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) bool isRemote = item && item->data(Qt::UserRole).toBool();
return;
// Use -D for force delete (handles unmerged branches) if (isRemote) {
auto *proc = gitProcess({QStringLiteral("branch"), QStringLiteral("-D"), branch}); // Remote branch: extract remote name and branch name (e.g. "origin/feature" -> "origin", "feature")
if (!proc) return; int sep = branch.indexOf(QLatin1Char('/'));
connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), if (sep <= 0) {
this, [this, proc, branch](int exitCode, QProcess::ExitStatus) { setMessage(QStringLiteral("Cannot determine remote for branch: ") + branch, true);
if (exitCode == 0) { return;
setMessage(QStringLiteral("Deleted branch: ") + branch);
} else {
QString err = QString::fromUtf8(proc->readAllStandardError()).trimmed();
setMessage(QStringLiteral("Delete failed: ") + err, true);
} }
proc->deleteLater(); QString remote = branch.left(sep);
refreshAll(); QString remoteBranch = branch.mid(sep + 1);
});
proc->start(); if (QMessageBox::question(m_toolView, QStringLiteral("Delete Remote Branch"),
QStringLiteral("Delete remote branch '%1' from '%2'?\n\nThis will remove it from the remote server.").arg(remoteBranch, remote),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
return;
auto *proc = gitProcess({QStringLiteral("push"), QStringLiteral("--delete"), remote, remoteBranch});
if (!proc) return;
connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, proc, branch](int exitCode, QProcess::ExitStatus) {
if (exitCode == 0) {
setMessage(QStringLiteral("Deleted remote branch: ") + branch);
} else {
QString err = QString::fromUtf8(proc->readAllStandardError()).trimmed();
setMessage(QStringLiteral("Delete failed: ") + err, true);
}
proc->deleteLater();
refreshAll();
});
proc->start();
} else {
// Local branch
if (QMessageBox::question(m_toolView, QStringLiteral("Delete Branch"),
QStringLiteral("Delete local branch '%1'?").arg(branch),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
return;
// Use -D for force delete (handles unmerged branches)
auto *proc = gitProcess({QStringLiteral("branch"), QStringLiteral("-D"), branch});
if (!proc) return;
connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, proc, branch](int exitCode, QProcess::ExitStatus) {
if (exitCode == 0) {
setMessage(QStringLiteral("Deleted branch: ") + branch);
} else {
QString err = QString::fromUtf8(proc->readAllStandardError()).trimmed();
setMessage(QStringLiteral("Delete failed: ") + err, true);
}
proc->deleteLater();
refreshAll();
});
proc->start();
}
} }
void GitPlusPlusView::compareBranch() void GitPlusPlusView::compareBranch()

View File

@ -173,6 +173,7 @@ private:
QString m_gitDir; QString m_gitDir;
QString m_currentBranch; QString m_currentBranch;
QStringList m_allBranches; QStringList m_allBranches;
QStringList m_remoteBranches;
bool m_isDirty = false; bool m_isDirty = false;
QPointer<QProcess> m_remoteProc; QPointer<QProcess> m_remoteProc;
int m_spinnerFrame = 0; int m_spinnerFrame = 0;